Antes de la migración 007 los dropdowns guardaban variantes parciales (ej. "Altafulla" en vez de "Julio Altafulla"). El filtro del Reporte de Horas no encontraba esos registros viejos porque users.name ahora es el nombre completo. Migración 013 hace un UPDATE con JOIN a users donde haya match único LIKE %legacy%. Aplicó a 4 filas: 2 en tramites_hours, 1 en tramites_obs, 1 en escrituras_tramites.
Donna — Portal interno AALN Law
Portal interno de la firma Arias, Abrego, López & Noriega (aalnlaw.com) que aloja herramientas internas para el equipo. Primer app embarcada: Control de Escrituras Públicas.
Producción: https://donna.aal-law.com
Arquitectura
Navegador
│ HTTPS
▼
Cloudflare → Caddy infra (SSL termination)
│ HTTP :80
▼
┌──────────────────────┐
│ Caddy (este server) │ /opt/donna/public
│ rewrite *.php │
└──────┬───────────────┘
│ unix socket
▼
┌──────────────────────┐
│ PHP-FPM 8.4 │
└──────┬───────────────┘
▼
┌──────────────────────┐
│ MariaDB 11.8 │ DB: donna
└──────────────────────┘
Stack:
- PHP 8.4-fpm (
/run/php/php8.4-fpm.sock) - Caddy 2.6 en
:80 - MariaDB 11.8 (
127.0.0.1:3306) - Autenticación OAuth2 contra Mailcow (
mail.aal-law.com) - Backups diarios automáticos vía systemd timer
Estructura del repo
donna/
├── public/ ← DocumentRoot
│ ├── index.php ← Landing + grid de apps
│ ├── auth/
│ │ ├── login.php ← Inicia OAuth flow
│ │ ├── callback.php ← Recibe code de Mailcow
│ │ └── logout.php
│ └── escrituras/
│ ├── index.php ← Auth-guard + sirve view
│ └── api.php ← CRUD trámites + historial
├── src/ ← Librerías PHP compartidas (fuera de DocumentRoot)
│ ├── Config.php ← Lee /etc/donna/donna.env
│ ├── Db.php ← PDO singleton
│ ├── Auth.php ← Sesión + OAuth guard + whitelist
│ ├── Oauth.php ← Cliente HTTP a Mailcow
│ └── Apps.php ← Catálogo de apps del menú
├── views/ ← Templates HTML servidos por PHP tras auth
│ └── escrituras.html
├── sql/ ← Migraciones (aplicar manualmente)
│ ├── 001_core.sql ← users
│ └── 002_escrituras.sql ← escrituras_tramites + escrituras_historial
├── bin/
│ └── donna-backup.sh ← mysqldump diario, retención 14 días
├── docs/ ← Diseños y archivos de infra de referencia
├── donna.env.example ← Plantilla del archivo de secrets
└── README.md
Fuera del repo (en el servidor):
/etc/donna/donna.env— secrets reales (640 root:www-data)/etc/caddy/Caddyfile— config del Caddy local/etc/systemd/system/donna-backup.{service,timer}— backups/var/log/donna/— access logs/var/backups/donna/— dumps SQL
Setup en un servidor nuevo
# 1. Paquetes del SO (Debian 13)
apt install -y caddy php8.4-fpm php8.4-mysql php8.4-curl \
php8.4-mbstring php8.4-intl php8.4-zip mariadb-server
# 2. Base de datos
mariadb -e "CREATE DATABASE donna CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mariadb -e "CREATE USER 'donna_app'@'localhost' IDENTIFIED BY 'XXX';
GRANT ALL ON donna.* TO 'donna_app'@'localhost';"
# 3. Código
git clone https://git.zhconsultores.com/ZH-Consultores/donna.git /opt/donna
chown -R www-data:www-data /opt/donna
# 4. Migraciones
mariadb donna < /opt/donna/sql/001_core.sql
mariadb donna < /opt/donna/sql/002_escrituras.sql
# 5. Secrets
mkdir -p /etc/donna
cp /opt/donna/donna.env.example /etc/donna/donna.env
# Editar /etc/donna/donna.env con valores reales (OAuth, DB_PASS, APP_SECRET)
chown root:www-data /etc/donna/donna.env
chmod 640 /etc/donna/donna.env
# 6. Caddy y backups: ver docs/infra/ para Caddyfile y systemd units
systemctl reload caddy
systemctl enable --now donna-backup.timer
OAuth2 con Mailcow
- En
https://mail.aal-law.com/admin→ Access → OAuth2 Apps - Crear app con
Redirect URI = https://donna.aal-law.com/auth/callback - Copiar
Client IDyClient Secreta/etc/donna/donna.env
El flujo:
/ ──login──▶ /auth/login.php ──redirect──▶ mail.aal-law.com/oauth/authorize
│
▼ ?code=...&state=...
/auth/callback.php ◀── redirect ◀── usuario autorizado
│
├── valida state (anti-CSRF)
├── intercambia code → token
├── lee profile (email, nombre)
├── valida dominio @aal-law.com o @aalnlaw.com
├── inserta/actualiza fila en `users`
└── redirige a /
Sesión: cookie donna_session, path=/, Secure, HttpOnly, SameSite=Lax, lifetime 8h. SSO automático entre todas las apps del portal.
Apps
Las apps se registran en src/Apps.php::catalog(). El landing las renderiza automáticamente como cards.
| Slug | Estado | Ruta | Tablas DB |
|---|---|---|---|
escrituras |
Live | /escrituras/ |
escrituras_tramites, escrituras_historial |
casos |
Coming soon | — | — |
agenda |
Coming soon | — | — |
clientes |
Coming soon | — | — |
Para agregar una app nueva:
- Crear
/opt/donna/public/<slug>/index.phpconAuth::requireAuth()arriba. - Crear
/opt/donna/public/<slug>/api.phpsi necesita backend. - Prefijar todas las tablas DB con
<slug>_. - Crear migración en
sql/00N_<slug>.sql. - Registrar en
src/Apps.php::catalog(). - Commit + push.
Identidad visual
| Token | Valor |
|---|---|
--navy |
#1B3A6B |
--navy-d |
#112446 |
--navy-l |
#2D5090 |
--gold |
#C9A84C |
--gold-l |
#E8C96A |
--gold-d |
#9E7C2E |
--off |
#F7F6F3 |
| Fuente headlines | DM Serif Display |
| Fuente UI | DM Sans |
Gradient hero del landing: #0E1F3F → #1B3A6B → #2D5090.
Backups
donna-backup.timer ejecuta diariamente a las 03:00 (con jitter de ±5min) y vuelca donna comprimido a /var/backups/donna/donna-YYYY-MM-DD_HHMMSS.sql.gz. Retención 14 días.
Restaurar:
gunzip -c /var/backups/donna/donna-2026-MM-DD_HHMMSS.sql.gz | mariadb donna
Operación
| Acción | Comando |
|---|---|
| Reload Caddy | systemctl reload caddy |
| Reload PHP-FPM | systemctl reload php8.4-fpm |
| Ver logs Caddy | tail -f /var/log/donna/access.log |
| Ver errores PHP | journalctl -u php8.4-fpm -f |
| Backup manual | /opt/donna/bin/donna-backup.sh |
| Próximo backup | systemctl list-timers donna-backup.timer |
Licencia
Software interno — sin licencia pública. Propiedad de Arias, Abrego, López & Noriega.