T ToutadminDocumentación

Instalar la edición PHP

La edición PHP se deposita tal cual en un alojamiento compartido: sin Composer, sin gestor de paquetes, sin servicio que mantener en marcha. Una carpeta, un archivo de configuración, una tarea programada. Este tutorial va de la carpeta vacía a la instancia en producción, incluido lo que la mayoría de las guías deja de lado: los permisos, el TLS, la tarea programada, las copias de seguridad y la actualización.

Lo que hace falta#

ElementoVersiónPor qué
PHP8.1 o más recienteTipos de unión, enum, readonly, never: el código los usa.
pdo_sqliteLa base entera. Sin ella no funciona nada.
mbstringTratamiento correcto de las cadenas en dieciséis idiomas.
opensslrecomendadaCifrado de los secretos en la base (contraseñas SMTP, tokens de externalización).
curlrecomendadaWebhooks salientes y envío de copias a un tercero.
intlrecomendadaFechas escritas como en la edición Node. Sin ella, los nombres de meses vienen de los diccionarios.

El asistente de instalación comprueba todo esto y lo muestra en pantalla antes de dejarle continuar: lo que es bloqueante detiene la instalación, lo que es opcional se señala sin bloquear.

Ninguna dependencia

No hay nada que instalar con composer: el lector IMAP, el archivo tar.gz, el PDF, el CSV, el código QR y el cliente HTTP están escritos dentro del producto. Es lo que permite depositarlo en un alojamiento donde no tiene línea de comandos.

1. Subir los archivos#

Obtenga el archivo comprimido de la edición PHP y súbalo. La carpeta es así:

toutadmin/
├── app/            el código (nunca servido por el servidor web)
│   ├── Core/       núcleo: enrutamiento, base, sesiones, seguridad
│   ├── Modules/    negocio: RR. HH., gestión, proyectos, calidad…
│   ├── Controllers/
│   ├── views/      plantillas
│   ├── locales/    los dieciséis diccionarios
│   └── schema.sql  el esquema, idéntico al de la edición Node
├── public/         ← la raíz web apunta aquí, y a ningún otro sitio
│   ├── index.php   el controlador frontal
│   ├── css/ js/
├── data/           base, caja fuerte, portafirmas, documentos recibidos, copias
├── tools/          cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
La raíz web apunta a public/, no a la carpeta

Es el punto más importante de esta página. Si el servidor sirve la carpeta entera, data/app.sqlite queda descargable por cualquiera: toda la base, huellas de contraseñas y caja fuerte incluidas. La carpeta data/ está por encima de public/ a propósito.

En un alojamiento compartido#

Se dan dos casos. Si su proveedor permite elegir la raíz de un dominio (cPanel, Plesk, y la mayoría de los alojamientos en modo «carpeta raíz»), suba la carpeta fuera de www/ y haga que el dominio apunte a toutadmin/public.

Si la raíz viene impuesta —a menudo www/ o public_html/— suba dentro el contenido de public/ y el resto por encima, y corrija luego la ruta al principio de public/index.php:

www/                  ← raíz impuesta
├── index.php
├── css/  js/
toutadmin/            ← el resto, fuera del alcance de la web
├── app/
├── data/
└── config.php
// www/index.php — la línea que hay que ajustar
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

Como último recurso, si no puede poner nada por encima de la raíz, el archivo .htaccess que va dentro de data/ ya deniega el acceso. Es un cinturón, no una solución: prefiera siempre la carpeta fuera de la raíz.

2. Escribir la configuración#

cp config.sample.php config.php

Luego abra config.php:

<?php
return [
    // Base SQLite y carpeta de datos: fuera de la raíz web.
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // Clave propia de la instancia. Se genera una vez:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // Token de instalación: véase el paso siguiente.
    'install_token' => '',

    // Dirección pública, para los enlaces enviados por correo.
    'base_url' => 'https://intranet.ejemplo.com',

    // Actívelo solo detrás de un proxy de confianza.
    'trust_proxy' => false,

    // Topes: intentos de acceso por cuarto de hora y por dirección,
    // peticiones por minuto y por dirección.
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // Caducidad: inactividad, y luego duración absoluta que nada prolonga.
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
La clave de la instancia

Sella las firmas del portafirmas y cifra los secretos en la base. Perderla invalida firmas. Si deja el valor de ejemplo, el producto genera una por su cuenta en data/.instance-key (permisos 0600): recuerde entonces incluir ese archivo en las copias de seguridad.

También puede mantener la configuración en otro sitio: la variable de entorno TOUTADMIN_CONFIG señala otro archivo.

3. Poner un token de instalación#

Entre la subida de los archivos y su paso por el asistente, la instancia es de quien la encuentre: el primero en llegar crea la cuenta de administración. La ventana es corta, y basta. Ponga, pues, un valor al azar antes de publicarla:

php -r "echo bin2hex(random_bytes(16));"
# luego en config.php:
'install_token' => 'c3f1…',

El asistente lo pide, lo compara en tiempo constante —una comparación ordinaria dejaría adivinar el token carácter a carácter— y registra cada rechazo. Sin el token no se crea nada. Una vez instalada la instancia, el asistente se cierra solo: el token puede quedarse, ya no sirve.

4. Permisos#

RutaPermisosQuién escribe
data/0770El servidor web, y solo él
data/app.sqlite0660Creado por la instalación
config.php0640Nadie: solo lectura para el servidor
app/, public/0755Nadie: solo lectura
# ejemplo en un servidor donde PHP corre como www-data
sudo chown -R usted:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public

SQLite escribe además archivos vecinos (-wal, -shm): es la carpeta la que debe ser escribible, no solo la base.

5. El servidor web#

Nginx#

server {
    listen 443 ssl http2;
    server_name intranet.ejemplo.com;

    root /var/www/toutadmin/public;   # y nada más
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/intranet.ejemplo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.ejemplo.com/privkey.pem;

    client_max_body_size 20M;          # depósitos en la caja fuerte, documentos recibidos

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Los archivos depositados no se sirven nunca directamente: pasan
    # por una ruta que verifica la sesión y la huella.
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

server {
    listen 80;
    server_name intranet.ejemplo.com;
    return 301 https://$host$request_uri;
}

Apache#

<VirtualHost *:443>
    ServerName intranet.ejemplo.com
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/intranet.ejemplo.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/intranet.ejemplo.com/privkey.pem

    <Directory /var/www/toutadmin/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

El archivo public/.htaccess que se entrega ya reenvía todas las direcciones a index.php. En un alojamiento compartido con Apache, es todo lo que hace falta.

TLS#

El producto pone la cabecera Strict-Transport-Security en cuanto la conexión está cifrada, y nunca en claro: anunciada desde una página sin cifrar no se leería, y encerraría una prueba local en https durante seis meses. En un servidor que usted administre:

sudo certbot --nginx -d intranet.ejemplo.com

Detrás de un proxy o un balanceador que termina el TLS, ponga 'trust_proxy' => true —y solo ahí—: de lo contrario cualquiera podría anunciar la dirección IP y el protocolo que quisiera en una cabecera.

6. La tarea programada#

Un sitio PHP solo funciona en el momento de una petición. Lo que la edición Node hace en su barrido horario —ocho operaciones— se hace aquí desde el cron del alojamiento:

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
Qué hace la tareaCon qué ritmo
Cierra las cuentas cuyo contrato ha vencidoen cada pasada
Emite las facturas de las suscripciones vencidasen cada pasada, sin facturar nunca dos veces
Convierte los vencimientos en notificacionesen cada pasada, sin duplicados
Purga las notificaciones leídas y el registro de auditoríasegún la conservación elegida
Recoge el buzón contable (IMAP)si la recogida está configurada
Vacía la cola de webhooks, con sus reintentosen cada pasada
Crea la copia automática y la externalizacuando ha transcurrido el intervalo

Llamar al script más a menudo que el intervalo configurado no hace copias más a menudo: nada se dispara antes de tiempo. En un alojamiento que limita el cron a una pasada cada quince minutos, sustituya * * * * * por */15 * * * *: los webhooks saldrán simplemente con ese retraso.

En un alojamiento sin cron

Algunos alojamientos de gama baja no lo ofrecen. Una llamada HTTP desde un servicio de terceros (un vigilante de disponibilidad y similares) no basta: cron.php se niega a ejecutarse fuera de la línea de comandos, precisamente para que una dirección pública no pueda disparar una copia de seguridad. En ese caso, la copia se lanza a mano desde la pantalla Copias de seguridad.

7. El asistente#

Abra su dominio. Mientras no exista ninguna cuenta, toda dirección redirige a /installation. El asistente cabe en una página:

  1. Los requisitos, comprobados y mostrados: lo que bloquea se marca como tal.
  2. El token de instalación, si está configurado.
  3. La organización: el nombre que se muestra en todas partes, y el número de días de vacaciones anuales atribuido a cada nuevo empleado no autónomo.
  4. El idioma por defecto de la instancia, entre los 16.
  5. La cuenta de administración: dirección y contraseña de al menos doce caracteres.

Todo se escribe en una sola transacción. En cuanto existe una cuenta, /installation reenvía a la página de acceso: el asistente se ha cerrado solo, no hay ningún archivo que borrar a mano.

8. Comprobar que todo funciona#

# las pruebas, en la máquina (no escriben en su base)
php tests/run.php

# las traducciones: ninguna pantalla usa una clave ausente de los diccionarios
php tools/check-keys.php

Luego, en la interfaz: cree un miembro, entre con él, deposite un adjunto, lance una copia manual y verifíquela desde la pantalla Copias de seguridad. Esos cuatro gestos tocan la base, los archivos, los permisos y el archivo comprimido; es decir, todo lo que el alojamiento debe saber hacer.

9. Actualizar#

# 1. una copia de seguridad primero, siempre
php tools/cron.php                     # o la pantalla Copias de seguridad

# 2. sustituir el código, no los datos
#    app/ public/ tools/ tests/ VERSION  ← sustituidos
#    data/ config.php                    ← conservados tal cual

# 3. la base se actualiza sola en la primera petición

El esquema evoluciona por migraciones idempotentes: repetir la actualización no rompe nada. Una instancia de la edición Node y una de la edición PHP comparten el mismo esquema —142 tablas, 1311 columnas— y las mismas contraseñas: una base pasa de una a otra sin conversión.

10. Hacer copias de verdad#

El archivo tar.gz lleva la base (copiada mediante VACUUM INTO, por tanto coherente incluso durante una escritura) y las cinco carpetas de archivos: fotos de perfil, CV recibidos, caja fuerte, portafirmas, documentos recibidos. Cada archivo lleva su huella SHA-256, verificada al restaurar.

Un archivo que se queda en el servidor que protege no protege nada: configure un destino remoto (FTPS o Google Drive) desde la pantalla Copias de seguridad. El detalle está en la página Copias de seguridad y restauración.

Probarlo primero en local#

cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php

El servidor integrado de PHP basta para descubrir el producto. No sirve para producción: un solo proceso, sin TLS, sin límite de carga.

Documentación de Toutadmin, construida el 2026-09-13. Sitio autónomo, independiente del software.