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.
- Migración 012: ALTER TABLE users DROP COLUMN orden
- Lookups: abogadoNames y allUsers ordenan por name ASC
- API admin: create/update_user dejan de leer/escribir orden
- UI admin: removida la columna "Orden" de la tabla y el input
"Orden" del modal de usuario
- Nueva tabla calendar_events (tipo, fecha, hora_inicio/fin, ubicación,
descripción, creador) — migración 011
- API CRUD /calendario/api.php (list/create/update/delete) con AuditLog
- Permisos: cualquiera autenticado crea; solo creador o Socio/Admin
edita y elimina
- UI calendario: chip "Eventos", botón "+ Nuevo evento" en toolbar y
en vista Día, modal con tipo/fecha/horario/ubicación, opción
"Todo el día"
- Detalle de evento manual muestra creador, horario y ubicación;
acciones Editar/Eliminar inline (sin navegación) si tiene permiso
- Colores por tipo: reserva sala, cumpleaños, recordatorio, evento
NUEVA INFRAESTRUCTURA
* SQL 010: tabla audit_log (id, fecha, user_email, user_name, modulo,
accion, entity_type, entity_id, entity_label, detalles JSON, ip_addr)
con índices por fecha/user/modulo/accion/entity.
* src/AuditLog.php: helper estático con dos métodos:
- log() lee actor de $_SESSION (uso normal post-auth)
- logAs() recibe email/name explícito (para login/logout y CLI)
Falla silenciosamente si la DB no responde — no rompe la operación
principal por un fallo de logging.
HOOKS EN MUTACIONES
* Auth.php: registra 'login' al completar OAuth y 'logout' antes de
destruir la sesión.
* escrituras/api.php: registra create/update/delete con id+nombre.
* tramites/api.php: registra 8 mutaciones — create/update/delete del
trámite, set_status (con nuevo estatus en detalles), add_obs,
add_hours/update_hours/delete_hours (con abogado y horas en
detalles), todos referenciados por codigo (ADM-001, etc.).
* admin/api.php: registra create_user/update_user/delete_user, con
diff de cambios en update_user.
* GETs (list_*) no se loguean — serían ruido.
LECTOR
* admin/api.php nuevo endpoint GET ?action=list_log que devuelve
entradas con filtros from/to/user/modulo/accion/search (LIKE)
+ catálogos para dropdowns. Límite 500 por query.
UI EN PANEL DE CONTROL
* admin.html ahora tiene tabs: "Usuarios" (existente) y "Registro de
transacciones" (nuevo).
* Tab Registro: toolbar de filtros (rango fechas, usuario, módulo,
acción, búsqueda libre, atajos Hoy/Semana/Mes/Todo) + tabla con
Fecha·Hora · Usuario · Módulo (pill coloreado) · Acción · Entidad
con label y tipo · Detalles JSON resumido.
TIMEZONE
* OS: timedatectl set-timezone America/Panama (era UTC). El reporte
de horas salía vacío para entries creados después de las 19:00 hora
Panama porque el filtro "este mes" del frontend usa local time pero
los datos se guardaban en UTC del día siguiente.
* MariaDB y PHP ahora corren en hora Panamá:
- src/Db.php hace SET time_zone='-05:00' por conexión.
- src/Config.php hace date_default_timezone_set('America/Panama').
* Migración 009: convierte timestamps existentes UTC → Panama usando
CONVERT_TZ en todas las tablas con DATETIME (users, escrituras_*,
tramites_*).
EDIT/DELETE DE HORAS
* Backend tramites/api.php: nuevos endpoints
- PUT ?action=update_hours: cambia abogado, hrs y descripción
- DELETE ?action=delete_hours
Permisos: solo el autor_email original (quien registró) o un
Socio/Admin puede editar/eliminar. También se valida canView().
* Frontend tramites.html: cada entrada de horas ahora muestra botones
✎ (editar) y ✕ (eliminar) al lado de la fecha, visibles solo para
quien tiene permiso. Editar abre un modal pequeño dedicado con los
mismos campos (abogado dropdown, horas dropdown 15-min, descripción).
JERARQUÍA DE ROLES
admin = programador. Modo Dios. Oculto de dropdowns y del Panel
de Control para non-admins. Acceso total.
socio = modo Dios visible. Acceso al Panel, ve y edita todo.
abogado = crea casos, ve los suyos + donde es involucrado.
usuario = solo ve/edita donde es involucrado.
* Migración 008: agrega 'admin' al ENUM users.role. Promueve a
ozerpa@aal-law.com como admin.
* Auth.php: isAdmin() nuevo. isSocio() y isAbogado() ahora incluyen
admin (admin hereda todos los poderes de socio). canCreate/canDelete
también heredan.
* Lookups.php:
- abogadoNames() ya filtraba a role IN (socio, abogado) → admins no
aparecen en el dropdown "Abogado Solicitante".
- involucrablesUsers() agrega filtro role != 'admin' → admins no
aparecen en el picker de Involucrados.
- allUsers($includeAdmins) parametrizada: socios reciben la lista
sin admins, admins reciben todo.
* Bootstrap.php: window.DONNA.isAdmin disponible al frontend.
* admin/api.php:
- list_users pasa Auth::isAdmin() para filtrar.
- create_user / update_user: rechazan role='admin' si quien llama no
es admin. Solo admins pueden editar a otros admins.
- delete_user: protege admins (extends al check existente de socio).
- Self-protection extendida: admin no puede demoverse por debajo de
socio (queda en god-mode tier).
* admin.html: opción 'admin' en role dropdown solo visible si el
usuario actual es admin. Role pill navy/blanca para admin.
BUG FIX
* admin.html: el filtro <select id="f-activo"> y el modal <input id="f-activo">
tenían el MISMO id. document.getElementById devolvía el select (primer en
DOM order) y la lectura .checked siempre era undefined → JSON enviaba el
valor stale del select. Renombré el filtro a `f-activo-filter`.
REFACTOR: drop apellido, usar Nombre Completo
* Migración 007: copia apellido→name para placeholders, migra
escrituras_tramites.abogado y tramites_legales.abogado a usar nombre
completo, DROP COLUMN apellido + índice.
* Lookups: abogadoApellidos() → abogadoNames() filtra socios+abogados
activos con name no vacío.
* Auth.php: drop método apellido(), agregado userName().
* tramites/api.php: visibilidad usa users.name = t.abogado.
* admin/api.php: drop apellido en create/update payloads.
* admin.html: drop columna y campo Apellido del table y form.
* Bootstrap envía window.DONNA.abogados con nombres completos.
LANDING (index.php)
* Gear icon en dash-header visible solo si Auth::isSocio() (chequeo
server-side, sin flash de JS).
CLEANUP
* Eliminado logging de debug en admin/api.php que se agregó para
diagnosticar el bug del activo.
PERMISOS (sin cambios — confirmación)
* Trámites Legales: solo socio/abogado pueden crear (canCreate() ya lo
enforza desde 2db8f23).
* Escrituras: cualquier rol autenticado puede crear (escrituras/api.php
nunca tuvo restricción).
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/.
Migración del frontend localStorage a backend autenticado bajo Donna.
* SQL 004: tramites_legales (todos los campos por dept en columnas
nullable + ENUMs Sí/No), tramites_obs (bitácora N:1), tramites_hours
(registro de horas N:1), tramites_seq (contador por departamento).
IDs server-side tipo ADM-001 / CRP-001 / MIG-001 / LIT-001 con
SELECT FOR UPDATE para atomicidad.
* public/tramites/api.php: list/create/update/delete + set_status +
add_obs + add_hours. Valida departamento y estatus contra los enums
del frontend. Audita creado_por / actualizado_por con el email
autenticado.
* views/tramites.html: artifact original adaptado. Quitado backup/restore
localStorage (mysqldump diario ya hace ese trabajo). Cada mutación
va vía fetch con manejo de 401 → redirect a login. Mapeo
id<->codigo para preservar el contrato del artifact.
* Apps.php: la card "Gestión de Casos" placeholder se reemplaza por
"Control de Trámites Legales" en estado live.
* SQL: nueva migración 003 que añade `entrada VARCHAR(13)` después de
`estatus` en `escrituras_tramites`. Aplicada en producción.
* API: INSERT/UPDATE persisten el campo; auditoría lo registra con
label "Entrada" en el historial.
* Frontend:
- Nueva columna en el header entre Estatus y Últ. Actualización
- Input con `pattern=^\d{1,8}/\d{4}$`, `maxlength=13`, placeholder
"########/####" y validación nativa del navegador
- newRow() y duplicateRow() incluyen `entrada:''` por defecto
- exportHeaders / rowToArray / Excel widths actualizados
- pdfCols renumerado para incluir Entrada en el PDF
Setup completo del portal interno donna.aal-law.com:
* Landing público con OAuth login + grid de apps autenticado
* Flujo OAuth2 contra Mailcow (mail.aal-law.com)
* Sesiones SSO compartidas en el dominio (cookie donna_session, 8h)
* Whitelist de dominios aal-law.com / aalnlaw.com
* App "Control de Escrituras" migrada desde ZH-Consultores/control-escrituras
(mismo schema, mismo frontend, ahora con shared Auth/Db/Config libs)
* Migraciones SQL para users, escrituras_tramites, escrituras_historial
* Caddy on :80 con rewrite extless → .php
* Backup diario MariaDB (systemd timer + script) con retención 14d
* Documentación de arquitectura en README + infra files en docs/infra