* admin/api.php: nuevo endpoint DELETE ?action=delete_user con salvaguardas:
- No te puedes eliminar a ti mismo
- No puedes eliminar a otro Socio (cambiar su rol primero)
- CASCADE de tramites_involucrados limpia vínculos N:M automáticamente
- Strings históricos (creado_por, abogado, autor_email) permanecen como
referencia muerta para preservar trazabilidad de trámites antiguos
* admin.html: botón rojo "Borrar usuario" en el footer del modal, visible
solo en modo edición (no en crear). Confirmación contextual según si es
placeholder o usuario real. Pista en el hint del email sobre usar Borrar
cuando hay conflicto.
* Fix: removido onclick del modal-overlay que cerraba el modal al hacer
clic en el padding del backdrop. Ahora solo cierra con ✕, Cancelar,
Esc, o Guardar exitoso. Más predecible.
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.