# Arquitectura del servidor SOM

> Inventario técnico saneado. Verificado y actualizado: 3 de septiembre de 2026
> (America/Santiago).
>
> Este documento describe componentes y relaciones. No contiene credenciales, tokens,
> claves privadas ni parámetros sensibles de acceso.

## Propósito

El servidor mantiene un núcleo estable formado por Nginx, Moodle, PostgreSQL y
Ollama. Docker aloja servicios auxiliares y, progresivamente, el backend SOM.

SOM comienza como un monolito modular Django. Sus módulos internos establecen
límites funcionales que más adelante podrán extraerse como microservicios sin
acoplarlos directamente a las tablas o archivos internos de Moodle.

## Vista general

```text
Internet
   |
   v
UFW
   |
   v
Nginx (TLS y enrutamiento)
   |-- :443  /              -> Moodle -> PHP-FPM -> PostgreSQL local
   |-- :443  /arqui/        -> documentación estática (compatibilidad)
   |-- :443  doc.minayao.site -> documentación estática (preferida)
   |-- :443  som.minayao.site -> SOM Django (Docker, loopback :8000)
   |-- :443  locker.minayao.site -> Learning Locker (Docker, loopback :8081)
   |-- :443  yet.minayao.site    -> Yet SQL LRS (Docker, loopback :8080)
   |-- :443  kuma.minayao.site -> Uptime Kuma (Docker, loopback :3002)
   |-- :443  portainer.minayao.site -> Portainer (Docker, loopback :9444)
   |-- :3001                -> compatibilidad Uptime Kuma
   `-- :9443                -> compatibilidad Portainer

Host HTTP/HTTPS desconocido -> https://minayao.site (Moodle)

Ollama
   `-- API privada en loopback :11434

Docker
   |-- Portainer
   |-- Uptime Kuma
   |-- SOM Django -> PostgreSQL privado de SOM
   |-- Learning Locker -> MongoDB y Redis privados
   `-- Yet SQL LRS -> PostgreSQL privado
```

## Inventario actual

| Capa | Componente | Versión/estado | Exposición |
| --- | --- | --- | --- |
| Sistema | Ubuntu Server | 24.04.4 LTS | Host |
| Entrada web | Nginx | 1.24.0, activo | 80/443, 3001 y 9443 |
| LMS | Moodle | 5.2.1 | HTTPS principal mediante Nginx |
| Aplicación PHP | PHP-FPM | 8.3 | Socket Unix local |
| Datos Moodle | PostgreSQL | 16.14 | Sólo localhost:5432 |
| IA | Ollama | 0.32.6 | Sólo localhost:11434 |
| Contenedores | Docker Engine | 29.7.2 | Daemon local |
| Administración | Portainer CE | Contenedor activo | HTTPS 9443 mediante Nginx |
| Monitorización | Uptime Kuma 2 | Contenedor saludable | HTTPS 3001 mediante Nginx |
| Aplicación | SOM Django | Contenedor saludable | HTTPS bajo `/som/` |
| Datos SOM | PostgreSQL | 16.14, contenedor saludable | Sólo red Docker interna |
| Aplicación | Learning Locker | 7.1.1, contenedores saludables | `locker.minayao.site` mediante Nginx |
| xAPI | Learning Locker xAPI Service | 7.1.2, contenedor saludable | Bajo el mismo subdominio |
| Aplicación | Yet Analytics SQL LRS | 0.9.7, contenedor saludable | `yet.minayao.site` mediante Nginx |
| Datos LRS | MongoDB, Redis y PostgreSQL | Contenedores privados | Sin puertos publicados |

## Catálogo de entradas públicas

| Nombre o ruta | Servicio | Backend real | Datos principales |
| --- | --- | --- | --- |
| `https://minayao.site/` | Moodle | Nginx + PHP-FPM del host | PostgreSQL del host + `/var/moodledata` |
| `https://som.minayao.site/` | SOM | `127.0.0.1:8000` | PostgreSQL `app-db-1` |
| `https://locker.minayao.site/` | Learning Locker | `127.0.0.1:8081` | MongoDB, Redis y volúmenes de archivos |
| `https://yet.minayao.site/` | Yet SQL LRS | `127.0.0.1:8080` | PostgreSQL `yet-lrsql-postgres-1` |
| `https://kuma.minayao.site/` | Uptime Kuma | `127.0.0.1:3002` | Volumen `uptime-kuma` |
| `https://portainer.minayao.site/` | Portainer | `127.0.0.1:9444` | Volumen `portainer_data` + socket Docker |
| `https://doc.minayao.site/` | Documentación | Archivos estáticos del host | `/opt/server-architecture` |
| `https://minayao.site/arqui/` | Documentación (compatibilidad) | Archivos estáticos del host | `/opt/server-architecture` |

El DNS de subdominios incluye resolución comodín. Nginx define por ello un
`default_server`: cualquier nombre no reconocido redirige al home de Moodle y no
se entrega accidentalmente a un backend. Cada aplicación válida conserva un
`server_name` explícito. El nombre anterior `lrs.minayao.site` se conserva sólo
como redirección permanente a `yet.minayao.site` para enlaces existentes.

## Núcleo del host

### Nginx

- Es el único punto de entrada HTTP/HTTPS.
- Termina TLS con certificados Let's Encrypt para la IP pública y los dominios.
- Redirige HTTP a HTTPS.
- Envía Moodle a PHP-FPM mediante un socket Unix.
- Actúa como proxy inverso para los servicios Docker publicados.
- Publica Learning Locker y Yet SQL LRS mediante virtual hosts HTTPS separados.
- La renovación del certificado se ejecuta mediante el temporizador de Certbot.

### Moodle

- Código: `/var/www/moodle`.
- Raíz pública: `/var/www/moodle/public`.
- Datos persistentes: `/var/moodledata`.
- Ejecución: Nginx y PHP-FPM, fuera de Docker.
- Cron: tarea de Moodle ejecutada cada minuto como `www-data`.
- Base: PostgreSQL local dedicada a Moodle.

Moodle debe tratarse como sistema externo desde SOM. La integración recomendada
es mediante web services, tokens de alcance limitado, eventos o una capa puente.
Los servicios auxiliares no deberían escribir directamente en tablas de Moodle.

Flujo: navegador → Nginx → socket Unix de PHP-FPM → código Moodle → PostgreSQL
local. Los archivos aportados por usuarios quedan fuera de la raíz web en
`/var/moodledata`. Moodle es el home canónico y no está dentro de Docker.

### PostgreSQL

- Clúster PostgreSQL 16 instalado en el host.
- Escucha exclusivamente en loopback.
- La autenticación TCP local utiliza SCRAM-SHA-256.
- Actualmente almacena la base de Moodle.

Cada servicio futuro debe ser propietario de sus datos. Compartir el mismo motor
puede ser válido, pero no se deben compartir esquemas ni credenciales de Moodle.

### Ollama

- Servicio systemd ejecutado con un usuario dedicado.
- API restringida a `127.0.0.1:11434`.
- No tiene exposición directa a Internet.

Los contenedores no pueden consumir el loopback del host directamente. El acceso
futuro desde SOM debe pasar por una interfaz interna controlada, con autenticación,
límites de recursos, tiempos máximos y registro de solicitudes.

## Plataforma Docker

### Servicios actuales

| Servicio | Backend local | Persistencia | Entrada pública |
| --- | --- | --- | --- |
| Uptime Kuma | `127.0.0.1:3002` | Volumen Docker dedicado | Nginx TLS :3001 |
| Portainer | `127.0.0.1:9444` | Volumen Docker dedicado | Nginx TLS :9443 |
| SOM Django | `127.0.0.1:8000` | Imagen reproducible | Nginx TLS `/som/` |
| PostgreSQL SOM | Red interna Docker :5432 | Volumen `app_postgres_data` | Sin entrada pública |
| Learning Locker | `127.0.0.1:8081` | Cinco volúmenes dedicados | Nginx TLS `locker.minayao.site` |
| Yet SQL LRS | `127.0.0.1:8080` | Volumen `yet-lrsql-postgres-data` | Nginx TLS `yet.minayao.site` |
| MongoDB y Redis de Learning Locker | Redes internas | Volúmenes dedicados | Sin entrada pública |
| PostgreSQL de Yet SQL LRS | Red `yet-lrsql-internal` | Volumen dedicado | Sin entrada pública |

Portainer tiene acceso al socket Docker y debe considerarse una consola con
privilegios administrativos. Su acceso debería restringirse por red, VPN o una
capa de autenticación adicional.

### Despliegue actual de SOM

SOM está desplegado como monolito modular Django:

```text
SOM Django
|-- API y autenticación
|-- integración Moodle
|-- identidad
|-- LRS/eventos
|-- instructores
|-- analítica
`-- integración IA/Ollama
```

Las entradas activas son:

```text
https://som.minayao.site/api/...
https://169.58.128.68/som/api/...  (compatibilidad)
```

Nginx publica `/som/` y el contenedor Django permanece enlazado únicamente a
loopback. El arranque espera a PostgreSQL, aplica migraciones, recopila archivos
estáticos e inicia Gunicorn con dos workers. Ambos contenedores tienen health
checks y políticas de reinicio.

El stack utiliza dos redes:

- `app_som_internal`: red interna compartida exclusivamente por Django y PostgreSQL.
- `app_som_egress`: salida de red para Django sin exponer PostgreSQL.

Los módulos podrán extraerse cuando exista una necesidad operacional
medible: escalado independiente, aislamiento de fallos, equipos separados o ciclos
de despliegue diferentes.

### Learning Locker

Learning Locker se despliega desde `/opt/docker-projects/locker/compose.yaml` como
el proyecto Compose `learning-locker`. El Nginx interno del stack es el único
contenedor publicado al host y escucha exclusivamente en `127.0.0.1:8081`.
Nginx del host termina TLS y publica `https://locker.minayao.site`.

El stack contiene MongoDB con replica set, Redis, migraciones, API, interfaz web,
worker, scheduler, xAPI Service y un Nginx interno. Los servicios de ejecución
continua usan `restart: unless-stopped`; MongoDB, Redis, API, interfaz, xAPI y el
proxy interno disponen de comprobaciones de salud.

La persistencia se reparte entre los volúmenes `learning-locker_mongo-data`,
`learning-locker_mongo-config`, `learning-locker_redis-data`,
`learning-locker_app-storage` y `learning-locker_xapi-storage`. MongoDB, Redis y
los puertos internos de aplicación no se publican en el host.

Learning Locker 7.1.1 y varios de sus componentes base están fuera de soporte.
Debe mantenerse aislado, respaldado y expuesto únicamente mediante Nginx y TLS.

#### Flujo y componentes de Learning Locker

```text
locker.minayao.site -> Nginx host (TLS) -> 127.0.0.1:8081 -> Nginx del stack
                                                            |-> ui
                                                            |-> api
                                                            `-> xapi
api/ui -> MongoDB              worker/scheduler -> Redis y tareas asíncronas
```

- `mongo`: MongoDB 4.4.29 con replica set; datos y configuración persisten en dos
  volúmenes y `27017` no se publica en el host.
- `mongo-init`: inicialización puntual del replica set, no un daemon permanente.
- `redis`: Redis 4.0.14 para coordinación y colas, con volumen propio y sin puerto público.
- `migrate`: ejecución puntual de migraciones antes del servicio normal.
- `api` y `ui`: API administrativa e interfaz; comparten almacenamiento persistente.
- `worker` y `scheduler`: tareas en segundo plano y planificadas; no son entradas web.
- `xapi`: xAPI Service 7.1.2, con volumen de almacenamiento propio.
- `nginx`: proxy interno y única publicación del proyecto, ligada a loopback.

Todos pertenecen a `learning-locker_locker-internal`. Ese nombre histórico no
implica el atributo Docker `internal: true`; el aislamiento efectivo de MongoDB,
Redis y las aplicaciones se obtiene porque no publican puertos. Sólo el proxy
interno publica `127.0.0.1:8081`.

### Yet Analytics SQL LRS

Yet SQL LRS se despliega desde `/opt/docker-projects/yet/compose.yaml` como el
proyecto Compose `yet-lrsql`. La aplicación escucha exclusivamente en
`127.0.0.1:8080`; Nginx del host publica `https://yet.minayao.site`. Su interfaz
administrativa está bajo `/admin` y el endpoint xAPI bajo `/xapi`.

PostgreSQL permanece dentro de la red `yet-lrsql-internal`, sin puertos publicados,
y conserva sus datos en el volumen `yet-lrsql-postgres-data`. Tanto PostgreSQL
como SQL LRS tienen health checks y política `restart: unless-stopped`.

```text
yet.minayao.site -> Nginx host (TLS) -> 127.0.0.1:8080 -> SQL LRS
                                                              `-> PostgreSQL :5432
```

- `lrsql`: imagen `yetanalytics/lrsql:v0.9.7`; ofrece administración y xAPI. No
  tiene volumen de datos propio porque el estado durable reside en PostgreSQL.
- `postgres`: PostgreSQL 16.15 Alpine, sólo accesible por la red del proyecto y
  persistido en `yet-lrsql-postgres-data`.
- `yet-lrsql-internal`: red compartida por ambos. No usa `internal: true`, pero
  PostgreSQL no publica ningún puerto.

Las credenciales se inyectan desde el entorno local del despliegue y no se
publican en esta documentación.

### Uptime Kuma

Contenedor único con imagen mayor `2`, health check y reinicio automático. Nginx
publica `127.0.0.1:3002` en `kuma.minayao.site`; HTTPS `:3001` se conserva por
compatibilidad. La configuración persiste en el volumen `uptime-kuma`.

### Portainer

Contenedor único publicado en `127.0.0.1:9444`. Nginx lo presenta en
`portainer.minayao.site` y mantiene `:9443` por compatibilidad. Su estado está en
`portainer_data`; también monta `/var/run/docker.sock`, por lo que comprometerlo
equivale operacionalmente a controlar Docker. Tiene reinicio automático, pero no
declara health check.

### Documentación

No es un contenedor. Nginx sirve `/opt/server-architecture/public` y el Markdown
canónico `/opt/server-architecture/architecture.md`. `doc.minayao.site` es la
entrada preferida y `/arqui/` permanece como compatibilidad; ambas usan los mismos
archivos para evitar copias divergentes.

La ruta `https://doc.minayao.site/credenciales/` contiene el inventario sensible
del entorno de prototipo. Está fuera del árbol público y Nginx exige autenticación
HTTP sobre TLS, impide caché e indexación y añade demora a intentos fallidos. La
clave de acceso y los valores almacenados no se incluyen en este documento.

### Rutas SOM verificadas

| Ruta | Estado actual |
| --- | --- |
| `/som/` | Interfaz de acceso disponible |
| `/som/api/health/` | Salud pública, responde JSON `200` |
| `/som/api/` | API REST navegable |
| `/som/lrs/` | Módulo LRS autenticado |
| `/som/admin/` | Administración Django |
| `/som/static/` | Archivos estáticos servidos por WhiteNoise |

## Límites de integración recomendados

1. Nginx controla toda exposición pública y TLS.
2. Moodle conserva su código, moodledata y base de datos.
3. SOM se comunica con Moodle mediante contratos API explícitos.
4. Ollama permanece privado y se consume mediante una interfaz interna controlada.
5. Cada servicio Docker declara imagen, red, volúmenes, salud y reinicio en Compose.
6. Las bases, credenciales y volúmenes no se comparten implícitamente.
7. Los trabajos largos de analítica se separan de las peticiones HTTP.
8. Los eventos y tareas asíncronas pueden introducirse antes de extraer microservicios.

## Persistencia y respaldo

Elementos que deben incluirse en una política de respaldo:

- Base PostgreSQL de Moodle mediante respaldo lógico consistente.
- `/var/moodledata`.
- Configuración de Moodle, Nginx y unidades systemd relevantes.
- Volúmenes de Portainer y Uptime Kuma.
- Volumen PostgreSQL `app_postgres_data` y futuros archivos persistentes de SOM.
- Los cinco volúmenes persistentes de Learning Locker.
- El volumen `yet-lrsql-postgres-data` de Yet SQL LRS.
- Manifiestos Compose y documentación de recuperación.

El inventario del 11 de agosto de 2026 no encontró una estrategia de respaldo de
Moodle visible en las ubicaciones estándar. Esto debe resolverse antes de ampliar
la plataforma.

## Observaciones operacionales

- Nginx permite peticiones grandes para Moodle, pero los límites globales actuales
  de PHP son menores; el límite efectivo de carga debe revisarse según el uso real.
- SOM ya dispone de un manifiesto Compose versionado; Portainer y Kuma todavía
  deben trasladarse a manifiestos reproducibles.
- Learning Locker y Yet SQL LRS disponen de manifiestos Compose reproducibles,
  redes privadas, health checks y políticas de reinicio.
- Los respaldos de los nuevos LRS deben definirse y probarse antes de almacenar
  información de producción que no pueda reconstruirse.
- Conviene fijar versiones de imágenes y crear redes Docker con responsabilidades
  claras: proxy, integración y datos.
- La documentación pública debe mantenerse saneada. Credenciales, tokens, claves,
  reglas internas detalladas y copias de configuración no deben publicarse aquí.

## Rutas públicas

| Ruta | Finalidad |
| --- | --- |
| `/` | Moodle |
| `/arqui/` | Esta documentación en HTML |
| `/arqui/architecture.md` | Fuente Markdown saneada |
| `https://doc.minayao.site/` | Entrada preferida de la documentación |
| `/som/api/` | API SOM desplegada mediante Nginx y Docker |
| `https://locker.minayao.site/` | Interfaz de Learning Locker |
| `https://locker.minayao.site/data/xAPI/` | Endpoint xAPI de Learning Locker |
| `https://yet.minayao.site/admin` | Administración de Yet SQL LRS |
| `https://yet.minayao.site/xapi` | Endpoint xAPI de Yet SQL LRS |
| `https://som.minayao.site/` | Aplicación SOM |
| `https://kuma.minayao.site/` | Uptime Kuma |
| `https://portainer.minayao.site/` | Portainer |
