ESQUEMA (migraciones 005 + 006)
* users: agrega role ENUM('socio','abogado','usuario'), apellido, activo, orden
* Seed: cabrego@aal-law.com como socio + 15 placeholders por apellido para
poblar el dropdown "Abogado Solicitante" hasta que los reales se logueen.
* tramites_involucrados: tabla N:M (tramite_id, user_id) con CASCADE delete.
AUTH (src/Auth.php)
* userRow() cachea el row del usuario actual por request.
* role(), isSocio(), isAbogado(), canCreate(), canDelete(), requireSocio().
* completeLogin() retorna bool: false si user.activo=0 → callback muestra
pantalla "cuenta deshabilitada" en lugar de loguear.
BOOTSTRAP (src/Bootstrap.php + src/Lookups.php)
* Bootstrap::inject(view) reemplaza readfile en los wrappers; inyecta
<script>window.DONNA={me,name,role,isSocio,isAbogado,canCreate,canDelete,
abogados,users}</script> antes de </head> para que el frontend tenga
contexto sin requests extra.
TRAMITES — visibilidad + involucrados
* api.php: list filtra por (socio | creador | abogado solicitante mapped via
users.apellido | involucrado) excepto socios que ven todo. Todas las
mutaciones validan canView(). create/delete validan rol. Persiste
involucrados vía syncInvolucrados().
* tramites.html: nueva columna "Involucrados" después de "Abogado", chip
picker multi-select en el modal, gear icon (oculto a non-socios), oculta
botones "+ Nuevo" y ✕ para usuarios sin permisos. Auto-incluye al
creador como involucrado por defecto.
* Calendario y demás apps consumen el API filtrado → heredan la
visibilidad sin código nuevo.
PANEL DE CONTROL (/admin/)
* index.php: requireSocio() + Bootstrap injection.
* api.php: list_users, create_user, update_user. Salvaguardas: el socio
no puede demoverse ni desactivarse a sí mismo.
* admin.html: tabla unificada de usuarios con filtros, modal CRUD,
resaltado de fila propia, placeholders identificados visualmente.
GEAR ICON
* Agregado en escrituras.html, tramites.html, calendario.html con
display:none por defecto; JS lo revela si window.DONNA.isSocio.
* Link a /admin/.
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.