docs(readme): actualizar al estado actual del sistema
- 5 apps live (escrituras, tramites, calendario, horas, admin) con descripción de cada una - 4 roles (admin, socio, abogado, usuario) y matriz de permisos - Patrón Bootstrap::inject + window.DONNA - AuditLog y canView() documentados - FK user_id en hours/obs - Módulo compartido DonnaExport (Excel + PDF) con API JS - Eventos de hoy en home - 14 migraciones SQL listadas en orden - Setup en server nuevo con aplicación batch de migraciones - Guía "agregar una app nueva" actualizada
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 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**.
|
||||
Portal interno de la firma **Arias, Abrego, López & Noriega** (aalnlaw.com) que aloja las herramientas operativas del equipo: control de escrituras y trámites legales, registro de horas, calendario, panel de administración y auditoría.
|
||||
|
||||
> Producción: <https://donna.aal-law.com>
|
||||
|
||||
@@ -32,41 +32,96 @@ Cloudflare → Caddy infra (SSL termination)
|
||||
|
||||
**Stack:**
|
||||
- PHP 8.4-fpm (`/run/php/php8.4-fpm.sock`)
|
||||
- Caddy 2.6 en `:80`
|
||||
- MariaDB 11.8 (`127.0.0.1:3306`)
|
||||
- Caddy 2.6 en `:80` (terminación SSL en Cloudflare → infra Caddy)
|
||||
- MariaDB 11.8 (`127.0.0.1:3306`), zona horaria `America/Panama` (UTC-5)
|
||||
- Autenticación OAuth2 contra Mailcow (`mail.aal-law.com`)
|
||||
- Frontend: HTML + JS vanilla con `window.DONNA` inyectado por `Bootstrap::inject()`
|
||||
- Exportación a Excel/PDF cliente-side con [SheetJS](https://github.com/SheetJS/sheetjs) y `window.print()`
|
||||
- Backups diarios automáticos vía systemd timer
|
||||
|
||||
---
|
||||
|
||||
## Roles y permisos
|
||||
|
||||
| Rol | Descripción | Visibilidad |
|
||||
|---|---|---|
|
||||
| `admin` | Programador / modo Dios oculto | No aparece en dropdowns ni en listados para socios |
|
||||
| `socio` | Acceso total al sistema | Ve todo, gestiona usuarios y auditoría |
|
||||
| `abogado` | Puede crear trámites y escrituras | Solo ve trámites donde es creador, abogado solicitante o involucrado |
|
||||
| `usuario` | Apoyo administrativo (pasantes, asistentes) | Solo ve trámites donde está involucrado; crea escrituras |
|
||||
|
||||
Helpers en `src/Auth.php`:
|
||||
- `Auth::isSocio()` — true para `admin` y `socio`
|
||||
- `Auth::isAdmin()` — solo `admin`
|
||||
- `Auth::canCreate()` — `socio`, `abogado`, `admin` (trámites)
|
||||
- `Auth::canDelete()` — igual que `canCreate`
|
||||
|
||||
Usuarios nuevos al loguearse por primera vez nacen con rol `usuario`.
|
||||
|
||||
---
|
||||
|
||||
## Estructura del repo
|
||||
|
||||
```
|
||||
donna/
|
||||
├── public/ ← DocumentRoot
|
||||
│ ├── index.php ← Landing + grid de apps
|
||||
├── public/ ← DocumentRoot
|
||||
│ ├── index.php ← Landing + grid de apps + "Eventos de hoy"
|
||||
│ ├── assets/
|
||||
│ │ ├── donna-export.css ← UI compartida del botón Exportar
|
||||
│ │ └── donna-export.js ← Helpers DonnaExport.toExcel / toPDF / attach
|
||||
│ ├── auth/
|
||||
│ │ ├── login.php ← Inicia OAuth flow
|
||||
│ │ ├── callback.php ← Recibe code de Mailcow
|
||||
│ │ ├── login.php ← Inicia OAuth flow
|
||||
│ │ ├── callback.php ← Recibe code de Mailcow + crea/actualiza user
|
||||
│ │ └── 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
|
||||
│ ├── admin/
|
||||
│ │ ├── index.php ← Auth-guard + view (solo socios/admin)
|
||||
│ │ └── api.php ← CRUD usuarios + query del audit log
|
||||
│ ├── calendario/
|
||||
│ │ ├── index.php
|
||||
│ │ └── api.php ← CRUD calendar_events (manuales)
|
||||
│ ├── escrituras/
|
||||
│ │ ├── index.php
|
||||
│ │ └── api.php ← CRUD escrituras + historial
|
||||
│ ├── horas/
|
||||
│ │ ├── index.php
|
||||
│ │ └── api.php ← Reporte de horas con filtros + agregaciones
|
||||
│ └── tramites/
|
||||
│ ├── index.php
|
||||
│ └── api.php ← CRUD trámites + observaciones + horas
|
||||
├── src/ ← Librerías PHP compartidas (fuera de DocumentRoot)
|
||||
│ ├── Apps.php ← Catálogo de apps del portal (filtra por isSocio)
|
||||
│ ├── AuditLog.php ← Helper para registrar acciones en audit_log
|
||||
│ ├── Auth.php ← Sesión + OAuth guard + roles + whitelist
|
||||
│ ├── Bootstrap.php ← Inyecta window.DONNA en cada view HTML
|
||||
│ ├── Config.php ← Lee /etc/donna/donna.env, fija TZ a Panama
|
||||
│ ├── Db.php ← PDO singleton, fija session TZ a -05:00
|
||||
│ ├── Lookups.php ← Queries para dropdowns (abogados, usuarios)
|
||||
│ └── Oauth.php ← Cliente HTTP a Mailcow
|
||||
├── views/ ← Templates HTML servidos por PHP tras auth
|
||||
│ ├── admin.html ← Panel de Control (2 pestañas: Usuarios + Log)
|
||||
│ ├── calendario.html ← Vista año/mes/semana/día con eventos manuales
|
||||
│ ├── escrituras.html
|
||||
│ ├── horas.html ← Reporte de horas con desglose por trámite
|
||||
│ └── tramites.html ← 4 tabs: Admin / Corp / Mig / Litigios
|
||||
├── sql/ ← Migraciones (aplicar manualmente, ordenadas)
|
||||
│ ├── 001_core.sql ← users
|
||||
│ ├── 002_escrituras.sql ← escrituras_tramites + escrituras_historial
|
||||
│ ├── 003_escrituras_entrada.sql
|
||||
│ ├── 004_tramites.sql ← tramites_legales + tramites_seq
|
||||
│ ├── 005_users_roles.sql ← Roles socio/abogado/usuario
|
||||
│ ├── 006_tramites_involucrados.sql
|
||||
│ ├── 007_drop_apellido.sql ← Nombre Completo en toda la app
|
||||
│ ├── 008_admin_role.sql ← Rol admin (programador)
|
||||
│ ├── 009_panama_timezone.sql ← Convertir timestamps históricos UTC → -05:00
|
||||
│ ├── 010_audit_log.sql ← Tabla audit_log
|
||||
│ ├── 011_calendar_events.sql ← Eventos manuales del calendario
|
||||
│ ├── 012_drop_users_orden.sql ← Simplificación: orden alfabético siempre
|
||||
│ ├── 013_normalize_abogado_names.sql ← Backfill nombres legacy
|
||||
│ └── 014_add_user_id_to_hours_obs.sql ← FK user_id en hours/obs
|
||||
├── 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
|
||||
│ └── 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
|
||||
```
|
||||
|
||||
@@ -79,6 +134,78 @@ donna/
|
||||
|
||||
---
|
||||
|
||||
## Apps
|
||||
|
||||
Registradas en `src/Apps.php::catalog($isSocio)`. El landing las renderiza como cards.
|
||||
|
||||
| Slug | Estado | Visible a | Ruta | Tablas DB |
|
||||
|---|---|---|---|---|
|
||||
| `escrituras` | Live | Todos | `/escrituras/` | `escrituras_tramites`, `escrituras_historial` |
|
||||
| `tramites` | Live | Todos (filtrado por `canView()`) | `/tramites/` | `tramites_legales`, `tramites_involucrados`, `tramites_obs`, `tramites_hours`, `tramites_seq` |
|
||||
| `calendario` | Live | Todos | `/calendario/` | `calendar_events` (+ deriva de escrituras/trámites) |
|
||||
| `horas` | Live | Todos (no-socios solo sus propias) | `/horas/` | `tramites_hours` (vista) |
|
||||
| `admin` | Live | Solo socios/admin | `/admin/` | `users`, `audit_log` |
|
||||
|
||||
### Control de Escrituras (`escrituras`)
|
||||
Seguimiento de trámites notariales: estado, abogado, notaría, alteración de turno, historial y auditoría. Estatus: Solicitado → Recibido → En Proceso → Protocolizado → En Registro → Inscrito.
|
||||
|
||||
### Control de Trámites Legales (`tramites`)
|
||||
4 departamentos (Administrativo, Corporativo/Civil, Migración, Litigios) con campos específicos por depto. Cada trámite tiene:
|
||||
- **Involucrados** (M:N con users): determinan acceso y dropdowns
|
||||
- **Observaciones** (`tramites_obs`): timeline de comentarios
|
||||
- **Horas Incurridas** (`tramites_hours`): registro en bloques de 15 min (0:15, 0:30… 12:00). Editable/eliminable por autor, involucrados o socios. Almacena `abogado` (string) + `user_id` (FK) para que el filtro siga funcionando si renombras a un usuario.
|
||||
|
||||
### Calendario (`calendario`)
|
||||
Vista año/mes/semana/día. Eventos derivados automáticamente de Escrituras (fecha de solicitud) y Trámites (solicitud, vencimiento migración, término procesal). Más **eventos manuales** (`calendar_events`): reserva de sala, cumpleaños, recordatorio, evento general — con horario, ubicación y creador.
|
||||
|
||||
### Reporte de Horas (`horas`)
|
||||
Filtros por rango de fechas y por usuario. Tarjetas por abogado expandibles → muestran el desglose de trámites con total de horas por trámite. KPIs (Total horas, Entradas, Personas). No-socios ven solo sus propias horas.
|
||||
|
||||
### Panel de Control (`admin`)
|
||||
Pestaña **Usuarios**: CRUD, asignación de roles, activación/desactivación. La columna placeholder original se va reemplazando conforme los abogados se loguean. Listas siempre alfabéticas.
|
||||
|
||||
Pestaña **Registro de Transacciones**: query de `audit_log` con filtros por fecha, usuario, módulo, acción y búsqueda libre. Cada login, logout, create, update, delete, set_status, add_obs, add_hours y similares queda registrado.
|
||||
|
||||
---
|
||||
|
||||
## Exportación a Excel + PDF
|
||||
|
||||
Módulo compartido en `public/assets/donna-export.{js,css}` con la misma UI (botón "Exportar ▾" + dropdown verde Excel / rojo PDF) en los 6 puntos del sistema:
|
||||
|
||||
| Módulo | Qué exporta |
|
||||
|---|---|
|
||||
| Escrituras | Trámites filtrados |
|
||||
| Trámites (cada tab) | Filas filtradas del tab activo |
|
||||
| Calendario | `visibleEvents()` (respeta chips de origen + búsqueda) |
|
||||
| Reporte de Horas | Entradas filtradas |
|
||||
| Panel · Usuarios | Lista visible (respeta filtros) |
|
||||
| Panel · Registro de Transacciones | Entradas cargadas |
|
||||
|
||||
API JS:
|
||||
```js
|
||||
DonnaExport.buttonHtml(id?) // returns HTML del botón + menú
|
||||
DonnaExport.attach(wrapId, { onExcel, onPDF })
|
||||
DonnaExport.toExcel({ filename, sheetName, headers, rows, columnWidths, onEmpty })
|
||||
DonnaExport.toPDF ({ filename, title, headers, rows, pdfCols?, orientation?,
|
||||
statusColIndex?, statusColors?, footer?, onEmpty })
|
||||
```
|
||||
|
||||
- **Excel**: `.xlsx` con header en navy, ancho de columnas personalizable, UTF-8 nativo.
|
||||
- **PDF**: abre ventana nueva con HTML estilizado + `window.print()` (paisaje A4 por defecto).
|
||||
|
||||
---
|
||||
|
||||
## Eventos de hoy (Home)
|
||||
|
||||
Debajo de las tarjetas de módulos, el portal muestra los eventos del día:
|
||||
- **Eventos manuales** (cumpleaños, sala de conferencias, recordatorios): visibles a todos
|
||||
- **Escrituras** con `fecha_solicitud = hoy`: visibles a todos
|
||||
- **Trámites** con `fecha_solicitud / vencimiento / término = hoy`: solo a creador, abogado solicitante o involucrado (mismo `canView()` que el módulo)
|
||||
|
||||
Cada card es clickable y abre la ficha correspondiente.
|
||||
|
||||
---
|
||||
|
||||
## Setup en un servidor nuevo
|
||||
|
||||
```bash
|
||||
@@ -86,27 +213,29 @@ donna/
|
||||
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
|
||||
# 2. Zona horaria del SO
|
||||
timedatectl set-timezone America/Panama
|
||||
|
||||
# 3. 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
|
||||
# 4. 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. Migraciones (en orden)
|
||||
for f in /opt/donna/sql/0*.sql; do mariadb donna < "$f"; done
|
||||
|
||||
# 5. Secrets
|
||||
# 6. 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
|
||||
# 7. Caddy y backups: ver docs/infra/ para Caddyfile y systemd units
|
||||
systemctl reload caddy
|
||||
systemctl enable --now donna-backup.timer
|
||||
```
|
||||
@@ -130,7 +259,8 @@ El flujo:
|
||||
├── intercambia code → token
|
||||
├── lee profile (email, nombre)
|
||||
├── valida dominio @aal-law.com o @aalnlaw.com
|
||||
├── inserta/actualiza fila en `users`
|
||||
├── inserta/actualiza fila en `users` (rol=usuario si es nuevo)
|
||||
├── registra login en audit_log
|
||||
└── redirige a /
|
||||
```
|
||||
|
||||
@@ -138,24 +268,45 @@ Sesión: cookie `donna_session`, `path=/`, `Secure`, `HttpOnly`, `SameSite=Lax`,
|
||||
|
||||
---
|
||||
|
||||
## Apps
|
||||
## Patrones de código
|
||||
|
||||
Las apps se registran en `src/Apps.php::catalog()`. El landing las renderiza automáticamente como cards.
|
||||
### `Bootstrap::inject($viewPath)`
|
||||
Cada `<app>/index.php` hace:
|
||||
```php
|
||||
require_once __DIR__ . '/../../src/Bootstrap.php';
|
||||
Auth::requireAuth();
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
Bootstrap::inject(__DIR__ . '/../../views/<app>.html');
|
||||
```
|
||||
Esto inyecta antes del primer `</head>`:
|
||||
```html
|
||||
<script>window.DONNA = {
|
||||
me: 'email@aal-law.com',
|
||||
name: 'Nombre Completo',
|
||||
role: 'socio',
|
||||
isAdmin: false,
|
||||
isSocio: true,
|
||||
canCreate: true,
|
||||
canDelete: true,
|
||||
abogados: ['Mario Arias', ...],
|
||||
users: [{id, email, name, role}, ...]
|
||||
};</script>
|
||||
```
|
||||
|
||||
| Slug | Estado | Ruta | Tablas DB |
|
||||
|---|---|---|---|
|
||||
| `escrituras` | Live | `/escrituras/` | `escrituras_tramites`, `escrituras_historial` |
|
||||
| `casos` | Coming soon | — | — |
|
||||
| `agenda` | Coming soon | — | — |
|
||||
| `clientes` | Coming soon | — | — |
|
||||
**Importante:** usa `strpos`/`substr` para reemplazar solo el primer `</head>` (no `str_replace`), porque algunos JS embebidos contienen `</head>` dentro de template literals que rompen el HTML si se inyecta ahí.
|
||||
|
||||
**Para agregar una app nueva:**
|
||||
1. Crear `/opt/donna/public/<slug>/index.php` con `Auth::requireAuth()` arriba.
|
||||
2. Crear `/opt/donna/public/<slug>/api.php` si necesita backend.
|
||||
3. Prefijar todas las tablas DB con `<slug>_`.
|
||||
4. Crear migración en `sql/00N_<slug>.sql`.
|
||||
5. Registrar en `src/Apps.php::catalog()`.
|
||||
6. Commit + push.
|
||||
### `AuditLog::log($modulo, $accion, $entityType, $entityId, $entityLabel, $detalles?)`
|
||||
Falla silenciosamente (try/catch) para no romper el flujo principal. Llamado desde cada API después de operaciones de escritura. Los detalles van como JSON.
|
||||
|
||||
### Visibilidad de trámites legales (`canView()`)
|
||||
```
|
||||
Socio/admin → siempre
|
||||
Resto → creador, o abogado solicitante (match por users.name = t.abogado),
|
||||
o involucrado (M:N en tramites_involucrados)
|
||||
```
|
||||
|
||||
### FK `user_id` en hours/obs
|
||||
Tanto `tramites_hours` como `tramites_obs` tienen `abogado VARCHAR(100)` (display) **y** `user_id INT NULL` (FK canónica `ON DELETE SET NULL`). Esto evita que renombrar un usuario en el Panel de Control desconecte sus entradas históricas de los filtros. Si lo borras, `user_id` queda NULL y el string `abogado` permanece como historia.
|
||||
|
||||
---
|
||||
|
||||
@@ -175,15 +326,21 @@ Las apps se registran en `src/Apps.php::catalog()`. El landing las renderiza aut
|
||||
|
||||
Gradient hero del landing: `#0E1F3F → #1B3A6B → #2D5090`.
|
||||
|
||||
Colores por departamento de trámites:
|
||||
- Admin: `#6c5b95` · Corp: `#9E7C2E` · Mig: `#2980b9` · Lit: `#722f37`
|
||||
|
||||
Colores por tipo de evento manual:
|
||||
- Reserva sala: `#2e7d8f` · Cumpleaños: `#c2185b` · Recordatorio: `#e67e22` · Evento: `#27ae60`
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
`donna-backup.timer` ejecuta diariamente a las 03:00 (jitter ±5min) y vuelca `donna` comprimido a `/var/backups/donna/donna-YYYY-MM-DD_HHMMSS.sql.gz`. Retención 14 días.
|
||||
|
||||
Restaurar:
|
||||
```bash
|
||||
gunzip -c /var/backups/donna/donna-2026-MM-DD_HHMMSS.sql.gz | mariadb donna
|
||||
gunzip -c /var/backups/donna/donna-YYYY-MM-DD_HHMMSS.sql.gz | mariadb donna
|
||||
```
|
||||
|
||||
---
|
||||
@@ -198,6 +355,26 @@ gunzip -c /var/backups/donna/donna-2026-MM-DD_HHMMSS.sql.gz | mariadb donna
|
||||
| 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` |
|
||||
| Aplicar migración | `mariadb donna < /opt/donna/sql/0NN_<nombre>.sql` |
|
||||
| Consultar audit log | Panel de Control → Registro de Transacciones |
|
||||
|
||||
---
|
||||
|
||||
## Agregar una app nueva
|
||||
|
||||
1. Crear `/opt/donna/public/<slug>/index.php` con `Bootstrap::inject(__DIR__ . '/../../views/<slug>.html')` precedido de `Auth::requireAuth()`.
|
||||
2. Crear `/opt/donna/public/<slug>/api.php` con `Auth::requireAuth()` (o `requireSocio()`).
|
||||
3. Crear `/opt/donna/views/<slug>.html` con el HTML + JS del módulo.
|
||||
4. Prefijar todas las tablas DB con `<slug>_` y crear migración numerada en `sql/0NN_<slug>.sql`.
|
||||
5. Registrar en `src/Apps.php::catalog()` (con flag `isSocio` si aplica).
|
||||
6. Si tiene exportación, incluir:
|
||||
```html
|
||||
<link rel="stylesheet" href="/assets/donna-export.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script src="/assets/donna-export.js"></script>
|
||||
```
|
||||
7. Para escritura de auditoría, llamar `AuditLog::log(...)` después de cada operación.
|
||||
8. Commit + push.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user