27 Commits
Author SHA1 Message Date
Soporte ZH Consultores bf93e8f79b fix(tramites): buscador incluye todas las columnas visibles
Antes solo miraba nombre/tipoTramite/tipoProceso/cliente/abogado/id,
así que buscar "Municipio" en la tab Administrativo no encontraba
nada aunque la columna Institución tuviera "Municipio de Panamá".

Nuevo rowHaystack(dept, r) concatena todos los campos que se
muestran en la tabla, excluyendo abogado y estatus (tienen sus
propios dropdowns al lado del buscador):

- Común:  id/codigo, cliente, nombre, instrucciones, fecha solicitud
- adm:    institución
- corp:   tipo documento
- mig:    tipo trámite, doc completa, permiso trabajo, dependientes,
          vencimiento
- lit:    tipo proceso, juzgado, circuito, esfera, instancia,
          doc completa, incidentes, amparo, término
- Todos:  involucrados (name + email)
2026-07-09 09:35:12 -05:00
Soporte ZH Consultores dbeacdde7b fix(tramites): buscador conserva el foco al tipear
Cada tecla en el input de búsqueda disparaba renderPanel() que
reescribía panel.innerHTML — incluyendo el input mismo — así que el
navegador descartaba el foco (y la posición del cursor). Los usuarios
tenían que hacer clic después de cada letra.

Cambios:
- Nuevo filterState por depto en memoria (no lee del DOM al filtrar)
- renderPanel() reconstruye todo solo cuando cambia el conjunto de
  datos (carga inicial, guardar, borrar, cambio de tab)
- Nuevo renderPanelBody() que solo repinta contadores + tbody
- Los handlers de search/status/abogado/esfera actualizan
  filterState y llaman renderPanelBody, dejando el toolbar intacto

Efecto: al tipear en el buscador, el input mantiene foco y cursor.
2026-07-09 09:30:09 -05:00
Soporte ZH Consultores 7e8500c7fb rebrand: AALN → AAL (Noriega salió de la sociedad)
- Todos los strings de branding actualizados:
  - "AALN" / "AALN Law" → "AAL"
  - "Arias, Abrego, López & Noriega" → "Arias, Abrego & López"
  - "M. Noriega" (placeholder en sample card del landing) → "M. Arias"
- Website link cambia de aalnlaw.com a aal-law.com en landing
  y footer del portal
- Filenames de exportación a Excel/PDF: *_AALN → *_AAL
- Footer del PDF compartido (DonnaExport.toPDF) actualizado
- Mensajes de denegación de OAuth callback actualizados
- README refleja nuevo nombre y URL del firm

OAuth sigue aceptando @aal-law.com y @aalnlaw.com como dominios
válidos (configurado en /etc/donna/donna.env) para no romper logins
durante la transición de correos.
2026-06-04 22:56:31 -05:00
Soporte ZH Consultores b8f5d83fee feat(export): botón Exportar (Excel + PDF) en Usuarios y Registro de Transacciones
- Usuarios y permisos: exporta los rows visibles (respeta filtros
  de rol, activos y búsqueda). Columnas: Email, Nombre, Rol,
  Activo, Último login
- Registro de transacciones: exporta las entradas cargadas en
  pantalla (respeta filtros de fecha/usuario/módulo/acción/texto).
  Excel incluye Email e IP; el PDF los omite para que quepa
- Mismo módulo compartido DonnaExport — UI idéntica a los otros
  módulos
2026-06-04 22:38:03 -05:00
Soporte ZH Consultores eff590af47 feat(export): UI unificada de Excel + PDF en los 4 módulos
- 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
2026-06-04 22:30:37 -05:00
Soporte ZH Consultores 3f922bc877 feat(horas): desglose por trámite en cada abogado + exportar a Excel
- 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
2026-06-04 22:10:05 -05:00
Soporte ZH Consultores 8ff56d3d78 refactor(users): eliminar columna orden, listar alfabéticamente
- 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
2026-06-04 21:31:02 -05:00
Soporte ZH Consultores d9adfbae09 ui(home): horas y admin como módulos; quitar Directorio de Clientes
- Apps.catalog() ahora recibe isSocio y emite "Reporte de Horas"
  (todos) y "Panel de Control" (solo socios/admin)
- Eliminado el placeholder "Directorio de Clientes"
- Removidos los iconos de reloj y engranaje del header en home,
  tramites, escrituras, calendario, horas y admin (la navegación
  ahora pasa por las tarjetas del portal)
- Iconos clock y gear agregados a la función icon() del home
2026-06-04 21:25:47 -05:00
Soporte ZH Consultores d26ab5e855 feat(calendario): eventos manuales — reservas, cumpleaños, recordatorios
- 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
2026-06-04 20:49:23 -05:00
Soporte ZH Consultores cd7595832f feat(admin): Registro de transacciones — log de auditoría de todo el sistema
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.
2026-06-04 20:30:46 -05:00
Soporte ZH Consultores 2f2d20604b feat(tramites): involucrados pueden editar y eliminar horas también
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.
2026-06-04 20:17:10 -05:00
Soporte ZH Consultores e98d791cb8 fix(tz): servidor en America/Panama + edit/delete de horas registradas
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).
2026-06-04 20:10:49 -05:00
Soporte ZH Consultores 72c04ad594 feat(horas): nuevo módulo Reporte de Horas + icono reloj en headers
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).
2026-06-05 00:56:10 +00:00
Soporte ZH Consultores bbf91c7bec feat(tramites): Horas Incurridas en bloques de 15 min con formato h:mm
* Input de horas pasa de <input type=number step=0.1> a un <select> con
  48 opciones de 0:15 a 12:00 en bloques de 15 minutos. Valor enviado
  al backend sigue siendo decimal (0.25 = 15 min, 1.5 = 1h30m).
* fmtHours() formatea decimal → "h" para horas enteras o "h:mm" para
  fraccionarias. Snap a 15 min para tolerar datos legacy.
* Display unificado:
  - Columna "Horas" en la tabla: muestra total en formato h:mm
  - Lista de entradas en detalle: cada entrada con su tiempo formateado
  - "Total" del card de Horas: en formato h:mm
* Aplica a los 4 departamentos.
2026-06-05 00:45:15 +00:00
Soporte ZH Consultores a01d1b29d0 fix(tramites): incluir también al Abogado Solicitante en dropdown de Horas
Refinamiento del commit anterior: el dropdown de "Horas Incurridas"
ahora incluye al Abogado Solicitante del trámite + los Involucrados
(deduplicado por nombre).

Si el trámite no tiene ni solicitante asignado ni involucrados,
inputs/botón quedan disabled con mensaje "— Sin asignados —".
2026-06-05 00:36:36 +00:00
Soporte ZH Consultores 729f1b4164 fix(tramites): dropdown de Horas Incurridas filtra solo a involucrados
En la ficha detalle de cada trámite (los 4 departamentos), la sección
"Horas Incurridas" listaba TODOS los abogados del sistema. Cambia a
listar solo los involucrados del trámite — que son quienes pueden
realmente trabajar el caso.

Si no hay involucrados aún, dropdown queda deshabilitado con texto
"— Sin involucrados —" y los inputs + botón "+" también disabled,
para que el usuario primero agregue involucrados desde el modal de
edición antes de registrar horas.
2026-06-05 00:36:02 +00:00
Soporte ZH Consultores 510304872c feat(auth): nuevo rol 'admin' (programador, modo Dios oculto)
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.
2026-06-05 00:27:54 +00:00
Soporte ZH Consultores 684c83f2c9 feat(admin): botón Borrar usuario + fix modal cierra al clic fondo
* admin/api.php: nuevo endpoint DELETE ?action=delete_user con salvaguardas:
  - No te puedes eliminar a ti mismo
  - No puedes eliminar a otro Socio (cambiar su rol primero)
  - CASCADE de tramites_involucrados limpia vínculos N:M automáticamente
  - Strings históricos (creado_por, abogado, autor_email) permanecen como
    referencia muerta para preservar trazabilidad de trámites antiguos
* admin.html: botón rojo "Borrar usuario" en el footer del modal, visible
  solo en modo edición (no en crear). Confirmación contextual según si es
  placeholder o usuario real. Pista en el hint del email sobre usar Borrar
  cuando hay conflicto.
* Fix: removido onclick del modal-overlay que cerraba el modal al hacer
  clic en el padding del backdrop. Ahora solo cierra con ✕, Cancelar,
  Esc, o Guardar exitoso. Más predecible.
2026-06-05 00:01:02 +00:00
Soporte ZH Consultores 072cc0b6e3 fix(admin): bug del activo + simplificar a Nombre Completo + gear en landing
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).
2026-06-04 23:42:06 +00:00
Soporte ZH Consultores 2db8f23ba5 feat(perms): permisología completa con 3 roles (Socio/Abogado/Usuario) + Panel de Control
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/.
2026-06-04 23:14:07 +00:00
Soporte ZH Consultores cd9dae98bb feat(calendario): app Calendario con 4 vistas + deep-link a apps origen
Cliente-side: el calendario hace fetch a /escrituras/api.php?action=list
y /tramites/api.php?action=list y agrega los eventos en memoria. Sin
backend nuevo (cero costo de mantenimiento, datos siempre frescos).

Eventos generados automáticamente:
* Escrituras → fecha_solicitud
* Trámites adm/corp/mig/lit → fecha_solicitud
* Trámites mig → fecha_vencimiento (resaltado como deadline)
* Trámites lit → fecha_termino (resaltado como deadline)

Vistas: Año (12 mini-meses con dots por evento + marca roja para
vencimientos), Mes (grid con chips ≤3 por día), Semana (7 columnas),
Día (lista de cards). Hoy resaltado en todas.

Resaltado de vencimientos:
* Vencido (días<0, status abierto) → rojo + animación flash
* ≤7d → naranja
* >7d → color por fuente

Funcionalidad adicional:
* Strip "Próximos vencimientos" con los 8 deadlines más urgentes
* KPIs: total · ≤7d · ≤30d · vencidos abiertos
* Filtros por fuente (chips) + búsqueda por texto
* Atajos: ←/→ mueve período, T hoy, Y/M/W/D cambia vista, Esc cierra
* Auto-refresh cada 60s en segundo plano (pausa si la modal está abierta)
* Misma barra superior estandarizada que Escrituras y Trámites

Popup ViewOnly al hacer click en un evento muestra ficha completa con
todos los campos relevantes según fuente y dept. Dos botones:
"Regresar al calendario" cierra · "Editar evento" navega a
/escrituras/?edit=ID o /tramites/?edit=CODIGO.

Deep-link: escrituras.html y tramites.html detectan ?edit=X al cargar
y auto-abren la modal de edición sobre esa fila. URL se limpia con
history.replaceState para que un refresh no re-abra la modal.

Apps.php: card placeholder "Agenda" reemplazada por "Calendario" live.
2026-05-11 20:48:20 +00:00
Soporte ZH Consultores 3f5027962a style(tramites): estandarizar barra superior con Control de Escrituras
* Header pasa de "h1 + hdr-btns chiquitos" al patrón de Escrituras:
  izq = botón "← Portal" + logo (mark + AALN Law + Control de Trámites
  Legales), der = sync-status + user-chip con avatar e iniciales +
  botón "Cerrar sesión".
* Mismo navy + 3px gold border-bottom, mismo height 58px, sticky top.
* Pulse animation en sync-dot (igual que Escrituras).
* Carga DM Serif Display + DM Sans para que la tipografía del header
  coincida con Escrituras.
* setUserChip() pobla avatar (iniciales) y nombre tras loadAll.
* Pestañas de departamentos quedan como estaban — solo el header se
  estandarizó.
2026-05-11 20:22:50 +00:00
Soporte ZH Consultores 3a342cb6a8 feat(escrituras): edición por modal + botón "← Portal"
* Reemplaza la edición inline (15 columnas con inputs editables) por
  un modal popup tipo Control de Trámites:
    - Click ✏️ o doble-click sobre la fila → abre el modal con el form
    - "Nuevo trámite" abre el mismo modal vacío
    - Duplicar abre el modal pre-poblado con "Copia — <nombre>"
    - Esc cierra el modal (prioritizando edición sobre historial)
    - Validación en el form: nombre requerido, patrón ########/####
      para Entrada antes de POST/PUT
* La tabla queda read-only — más limpia visualmente y sin riesgo de
  edits accidentales por click en una celda.
* Eliminadas funciones obsoletas: newRow/saveNewRow/cancelNewRow/
  toggleEditRow/saveExistingRow/getRowData/onFieldChange/onFieldBlur/
  onKeydown y el Set editingRows. Reemplazadas por openEditModal/
  closeEditModal/buildEditForm/saveEditModal.
* maybeFlushRefresh ahora solo chequea editModalOpen — más simple.
* Header: botón "← Portal" arriba a la izquierda (igual estilo de
  retorno que tiene Trámites Legales).
2026-05-11 20:12:46 +00:00
Soporte ZH Consultores 1bb63f12f6 feat(tramites): app "Control de Trámites Legales" + backend
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.
2026-05-11 20:03:28 +00:00
Soporte ZH Consultores da08e01f28 fix(escrituras): no interrumpir edición con el auto-refresh
Antes el poll de 15s llamaba render() y reconstruía toda la tbody,
borrando el modo edición en filas existentes (el guard isEditing solo
cubría filas nuevas).

Ahora:
* loadAll() detecta si hay edición en curso (isEditing || editingRows.size)
  y guarda los datos en allRows sin rerenderizar.
* Muestra "Edición en curso · refresco pausado" en el sync-status.
* maybeFlushRefresh() aplica los cambios remotos pendientes al salir
  del modo edición (cancelar o guardar la última fila).
* saveExistingRow ya llama a render() vía saveRow, así que limpia la
  bandera explícitamente.
2026-05-11 19:40:32 +00:00
Soporte ZH Consultores 81a909a1ec feat(escrituras): añadir columna "Entrada" (########/####)
* 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
2026-05-11 19:31:22 +00:00
Soporte ZH Consultores 4ee32aec6d Initial commit — Donna portal v1
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
2026-05-11 19:10:36 +00:00