Antes solo el autor original de la entrada (o un Socio/Admin) podía
editar/eliminar registros de horas. Ahora cualquiera con acceso al
trámite (creador, abogado solicitante, involucrado, socio, admin)
puede editar/eliminar horas de cualquier entrada — útil cuando un
involucrado nota un error en una hora registrada por otro.
* tramites/api.php: removido el check (autor_email == me OR isSocio)
de update_hours y delete_hours. canView() sigue siendo el único
gate, lo cual es consistente con add_hours.
* tramites.html: los botones ✎ y ✕ ahora siempre se muestran en cada
entrada de horas. El backend protege la operación.
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).
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.