Hasta ahora el vínculo "esta hora/observación es de X" era por string
(h.abogado = users.name). Se rompía cuando un Socio renombraba un
usuario en el Panel de Control (las entradas históricas conservaban
el nombre viejo y los filtros del Reporte de Horas no las hallaban).
- Migración 014: ADD COLUMN user_id INT NULL + FK ON DELETE SET NULL
en ambas tablas, con backfill via JOIN users.name = abogado
- tramites/api.php: helper userIdByName() + add_obs/add_hours/
update_hours ahora persisten user_id junto con el string abogado
- horas/api.php: filtro WHERE (user_id = :uid OR (user_id IS NULL
AND abogado = :name)) — prioriza FK, cae al string solo para
filas legacy sin user_id. by_user y by_user_tramite agrupan por
COALESCE(u.name, h.abogado) para usar siempre el nombre actual.
- Nuevos assets compartidos /assets/donna-export.{css,js}: botón
"Exportar ▾" + menú dropdown Excel/PDF y helpers DonnaExport.toExcel
/ .toPDF / .attach / .buttonHtml
- escrituras: refactor de exportExcel/exportPDF para usar el módulo
compartido (sin cambio de comportamiento)
- tramites: nuevo botón en la toolbar de cada tab (adm/corp/mig/lit),
exporta los rows filtrados de ese tab; PDF excluye Involucrados
- calendario: nuevo botón al final de la toolbar, exporta visibleEvents()
(respeta chips de origen y búsqueda)
- horas: reemplaza CSV server-side por XLSX client-side + agrega PDF;
removida la acción ?action=export del api.php
- API: by_user_tramite agrupa horas por (abogado, trámite) para
socios y para el propio abogado (no-socio)
- API: nueva acción ?action=export que devuelve CSV con BOM UTF-8
(Fecha · Abogado · Departamento · Código · Trámite · Cliente ·
Estatus · Horas · Descripción), respetando los mismos filtros
- UI: tarjetas de "Por persona" ahora son expandibles (clic en
la tarjeta) y muestran un mini-listado de trámites con total
de horas y cantidad de entradas por trámite
- UI: cada trámite del desglose es clickable y abre la ficha
- UI: botón verde "Exportar a Excel" en la barra de herramientas
NUEVA APP /horas/
* public/horas/index.php — Bootstrap::inject del view
* public/horas/api.php — endpoint GET ?action=list que retorna:
- entries: entradas detalladas con join a tramites_legales (link,
título, cliente, departamento, estatus)
- by_user: agregado por persona (solo socio/admin)
- avail_users: dropdown de usuarios para filtrar (solo socio/admin)
- total_hrs, is_socio, my_name
Filtros: ?from=YYYY-MM-DD, ?to=YYYY-MM-DD, ?user=ID
* views/horas.html — UI con:
- Header estandarizado (Portal, sync, user-chip, gear si socio, logout)
- Toolbar de filtros: rango de fechas + dropdown usuario (solo socio)
+ atajos rápidos (Hoy / Semana / Mes / Año / Todo)
- KPIs: Total horas, Entradas, Personas con horas (socio)
- Tarjetas "Por persona" (solo socio), click filtra por ese usuario
- Tabla detalle con link al trámite (?edit=CODIGO) + departamento +
horas en h:mm + descripción
VISIBILIDAD
* Abogado/usuario ve solo sus horas (match por h.abogado = mi_name).
* Socio/admin ve todas las horas. Puede filtrar por persona.
* Admin oculto del dropdown de personas (queries excluyen role=admin).
ICONO DE RELOJ EN HEADERS
* Agregado .btn-clock (alias visual de .btn-gear) en:
escrituras.html, tramites.html, calendario.html, admin.html,
horas.html, public/index.php (landing).
* Posición: al lado del user-chip, antes del gear/logout.
* Visible para todos los autenticados (no restringido por rol).