From dbeacdde7b40c580c6b303a7bebe13122a4e45f2 Mon Sep 17 00:00:00 2001 From: Soporte ZH Consultores Date: Thu, 9 Jul 2026 09:30:09 -0500 Subject: [PATCH] fix(tramites): buscador conserva el foco al tipear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- views/tramites.html | 114 +++++++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 27 deletions(-) diff --git a/views/tramites.html b/views/tramites.html index 38facee..8ec25f0 100644 --- a/views/tramites.html +++ b/views/tramites.html @@ -372,49 +372,64 @@ function checkDeepLink(){ function renderAll(){['adm','corp','mig','lit'].forEach(renderPanel);} -function renderPanel(dept){ - const panel=document.getElementById('panel-'+dept); - const rows=data[dept]; - const statuses=STATUS_MAP[dept]; - const search=(panel.querySelector('.search-input')||{value:''}).value.toLowerCase(); - const fStatus=(panel.querySelector('.f-status')||{value:''}).value; - const fAbog=(panel.querySelector('.f-abog')||{value:''}).value; - const fEsfera=(panel.querySelector('.f-esfera')||{value:''}).value; +// Estado de filtros por depto (persiste entre re-renders — no vive en el DOM, +// para que actualizar el no destruya el input de búsqueda). +const filterState = { + adm: { search:'', status:'', abog:'', esfera:'' }, + corp: { search:'', status:'', abog:'', esfera:'' }, + mig: { search:'', status:'', abog:'', esfera:'' }, + lit: { search:'', status:'', abog:'', esfera:'' }, +}; - let filtered=rows.filter(r=>{ - const txt=((r.nombre||r.tipoTramite||r.tipoProceso||'')+(r.cliente||'')+(r.abogado||'')+(r.id||'')).toLowerCase(); - if(search && !txt.includes(search)) return false; - if(fStatus && r.estatus!==fStatus) return false; - if(fAbog && r.abogado!==fAbog) return false; - if(dept==='lit' && fEsfera && r.esfera!==fEsfera) return false; +function computeFiltered(dept){ + const rows = data[dept]; + const f = filterState[dept]; + const q = f.search.toLowerCase(); + const filtered = rows.filter(r => { + const txt = ((r.nombre||r.tipoTramite||r.tipoProceso||'')+(r.cliente||'')+(r.abogado||'')+(r.id||'')).toLowerCase(); + if (q && !txt.includes(q)) return false; + if (f.status && r.estatus !== f.status) return false; + if (f.abog && r.abogado !== f.abog) return false; + if (dept === 'lit' && f.esfera && r.esfera !== f.esfera) return false; return true; }); - - const total=rows.length; - const activos=rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length; - const culminados=rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length; - window.__tramFiltered = window.__tramFiltered || {}; window.__tramFiltered[dept] = filtered; + return filtered; +} + +// Reconstruye toda la pestaña. Se usa solo cuando cambia el conjunto de datos +// (carga inicial, guardar/borrar, cambio de tab). NO se llama al teclear. +function renderPanel(dept){ + const panel = document.getElementById('panel-'+dept); + const rows = data[dept]; + const statuses = STATUS_MAP[dept]; + const f = filterState[dept]; + + const total = rows.length; + const activos = rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length; + const culminados = rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length; + const filtered = computeFiltered(dept); + panel.innerHTML=` -
+
${total}Total
${activos}Activos
${culminados}Cerrados
- - + - - ${ABOGADOS.map(a=>`${a}`).join('')} + ${ABOGADOS.map(a=>`${a}`).join('')} - ${dept==='lit'?` - CivilPenal + CivilPenal `:''} ${CAN_CREATE ? `` : ''} ${DonnaExport.buttonHtml('export-wrap-'+dept)} @@ -431,6 +446,51 @@ function renderPanel(dept){ onExcel: () => tramExportExcel(dept), onPDF: () => tramExportPDF(dept), }); + + // Handlers de filtro: al cambiar solo repintan la tabla, no el toolbar. + // Así el input de búsqueda conserva foco y posición del cursor al tipear. + const searchEl = panel.querySelector('.search-input'); + if (searchEl) searchEl.addEventListener('input', e => { + filterState[dept].search = e.target.value; + renderPanelBody(dept); + }); + const statusEl = panel.querySelector('.f-status'); + if (statusEl) statusEl.addEventListener('change', e => { + filterState[dept].status = e.target.value; + renderPanelBody(dept); + }); + const abogEl = panel.querySelector('.f-abog'); + if (abogEl) abogEl.addEventListener('change', e => { + filterState[dept].abog = e.target.value; + renderPanelBody(dept); + }); + const esferaEl = panel.querySelector('.f-esfera'); + if (esferaEl) esferaEl.addEventListener('change', e => { + filterState[dept].esfera = e.target.value; + renderPanelBody(dept); + }); +} + +// Solo actualiza contadores y tbody — deja el toolbar intacto. +function renderPanelBody(dept){ + const rows = data[dept]; + const filtered = computeFiltered(dept); + const counts = document.getElementById('counts-'+dept); + if (counts) { + const total = rows.length; + const activos = rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length; + const culminados = rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length; + counts.innerHTML = + `
${total}Total
`+ + `
${activos}Activos
`+ + `
${culminados}Cerrados
`; + } + const tbody = document.getElementById('tbody-'+dept); + if (tbody) { + tbody.innerHTML = filtered.length + ? filtered.map(r=>buildRow(dept,r)).join('') + : `
Sin registros. Haz clic en + Nuevo para agregar.
`; + } } // ── EXPORT (Trámites) ────────────────────────────────────────────────────