diff --git a/README.md b/README.md index 05644ee..1640885 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 `/index.php` hace: +```php +require_once __DIR__ . '/../../src/Bootstrap.php'; +Auth::requireAuth(); +header('Content-Type: text/html; charset=utf-8'); +Bootstrap::inject(__DIR__ . '/../../views/.html'); +``` +Esto inyecta antes del primer ``: +```html + +``` -| 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 `` (no `str_replace`), porque algunos JS embebidos contienen `` dentro de template literals que rompen el HTML si se inyecta ahí. -**Para agregar una app nueva:** -1. Crear `/opt/donna/public//index.php` con `Auth::requireAuth()` arriba. -2. Crear `/opt/donna/public//api.php` si necesita backend. -3. Prefijar todas las tablas DB con `_`. -4. Crear migración en `sql/00N_.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_.sql` | +| Consultar audit log | Panel de Control → Registro de Transacciones | + +--- + +## Agregar una app nueva + +1. Crear `/opt/donna/public//index.php` con `Bootstrap::inject(__DIR__ . '/../../views/.html')` precedido de `Auth::requireAuth()`. +2. Crear `/opt/donna/public//api.php` con `Auth::requireAuth()` (o `requireSocio()`). +3. Crear `/opt/donna/views/.html` con el HTML + JS del módulo. +4. Prefijar todas las tablas DB con `_` y crear migración numerada en `sql/0NN_.sql`. +5. Registrar en `src/Apps.php::catalog()` (con flag `isSocio` si aplica). +6. Si tiene exportación, incluir: + ```html + + + + ``` +7. Para escritura de auditoría, llamar `AuditLog::log(...)` después de cada operación. +8. Commit + push. ---