Rafael Lopez

descargas

# Lógica del Buscador
## Resumen
Buscador instantáneo con Fuse.js en cliente. Busca en clasificadores presupuestarios y navega a vistas de detalle.
## Decisiones de Diseño
### Motor de búsqueda
- **Tecnología**: Fuse.js (cliente)
- **Razón**: ~1,400 registros totales (~600 entidades + ~450 objetos + ~380 rubros), cabe en memoria, búsqueda instantánea sin latencia
### Configuración Fuse.js
```javascript
const fuseOptions = {
keys: [
{ name: 'nombre_normalizado', weight: 0.6 },
{ name: 'sigla_normalizada', weight: 0.2 },
{ name: 'codigo_normalizado', weight: 0.2 }
],
threshold: 0.4,
distance: 100,
includeScore: true,
includeMatches: true,
minMatchCharLength: 2,
ignoreLocation: true
};
```
### Tolerancia a errores
- Normalización de acentos ("andres" → "andrés")
- Case-insensitive ("UMSA" = "umsa")
- Fuzzy matching ("univercidad" → "universidad")
### Comportamiento
| Parámetro | Valor |
|-----------|-------|
| Debounce | 250ms |
| Mínimo caracteres | 2 |
| Máximo resultados | 15 |
| Navegación | Flechas ↑↓ + Enter + scroll de mouse |
## Estructura del Índice
### Índice unificado (implementado)
Todos los clasificadores en un solo array con `tipo`:
```javascript
// src/lib/services/search.js
const searchData = [
// Entidades
{
tipo: 'entidad',
codigo: 139,
nombre: 'Universidad Mayor De San Andrés',
sigla: 'UMSA',
contexto: 'Universidades Públicas',
años: 20,
nombre_normalizado: 'universidad mayor de san andres',
sigla_normalizada: 'umsa',
codigo_normalizado: '139'
},
// Objetos de gasto
{
tipo: 'objeto_gasto',
codigo: '25100',
nombre: 'Pasajes',
sigla: null,
contexto: 'Objeto del Gasto · Partida',
nivel: 'partida',
nombre_normalizado: 'pasajes',
sigla_normalizada: '',
codigo_normalizado: '25100'
},
// Rubros
{
tipo: 'rubro',
codigo: 13000,
nombre: 'INGRESOS POR IMPUESTOS',
sigla: null,
contexto: 'Rubro · Tipo',
nivel: 'tipo',
nombre_normalizado: 'ingresos por impuestos',
sigla_normalizada: '',
codigo_normalizado: '13000'
}
]
```
### Fuentes de datos
- **Entidades**: tabla `ppto.clas_institucional`
- **Objetos de gasto**: tabla `ppto.clas_objetos`
- **Rubros**: tabla `ppto.clas_rubros`
## Columnas de Búsqueda
### Entidades (clas_institucional)
| Columna | Buscar | Mostrar |
|---------|--------|---------|
| `desc_entidad` | ✓ | ✓ nombre principal |
| `sigla_entidad` | ✓ | ✓ entre paréntesis |
| `desc_area` | ✗ | ✓ contexto |
| `n_gestiones` | ✗ | ✓ "X años de datos" |
| `entidad` | ✗ | para URL |
### Objetos de gasto (clas_objetos)
| Columna | Buscar | Mostrar |
|---------|--------|---------|
| `desc_objeto` | ✓ | ✓ nombre principal |
| `objeto` | ✓ | ✓ código (monospace) |
| `nivel` | ✗ | ✓ contexto ("Objeto del Gasto · Partida") |
### Rubros (clas_rubros)
| Columna | Buscar | Mostrar |
|---------|--------|---------|
| `desc_rubros` | ✓ | ✓ nombre principal |
| `rubro` | ✓ | ✓ código (monospace) |
| `nivel` | ✗ | ✓ contexto ("Rubro · Tipo") |
## Flujo de Búsqueda
```
1. CARGA INICIAL
App monta → descarga clasificadores → construye índice Fuse.js
2. USUARIO ESCRIBE
Input → debounce 250ms → si ≥2 chars → Fuse.search()
3. RESULTADOS
Fuse devuelve matches → renderizar lista con highlighting
4. SELECCIÓN
Click o Enter → navegar a URL según tipo
```
## Estructura de URLs
```
/entidad/[codigo] → /entidad/139
/objeto/[codigo] → /objeto/25100
/rubro/[codigo] → /rubro/13000
```
## Formato de Resultados
Resultados agrupados por tipo de clasificador:
```
┌─────────────────────────────────────────────────────┐
│ 🔍 [Buscar entidad, objeto de gasto o rubro...] │
├─────────────────────────────────────────────────────┤
│ ENTIDADES │
├─────────────────────────────────────────────────────┤
│ Universidad Mayor De San Andrés (UMSA) │
│ Universidades Públicas │
├─────────────────────────────────────────────────────┤
│ OBJETOS DEL GASTO │
├─────────────────────────────────────────────────────┤
│ 25100 Pasajes │
│ Objeto del Gasto · Partida │
├─────────────────────────────────────────────────────┤
│ RUBROS │
├─────────────────────────────────────────────────────┤
│ 13000 INGRESOS POR IMPUESTOS │
│ Rubro · Tipo │
└─────────────────────────────────────────────────────┘
```
### Diferencias visuales
- **Entidades**: Nombre + (sigla) en primera línea
- **Objetos de gasto**: Código (monospace) + Nombre en primera línea
- **Rubros**: Código (monospace) + Nombre en primera línea
## Estados del Buscador
| Estado | Qué mostrar |
|--------|-------------|
| Vacío (< 2 chars) | Placeholder o sugerencias |
| Cargando índice | Spinner / "Cargando..." |
| Buscando | Nada (es instantáneo) |
| Con resultados | Lista de resultados |
| Sin resultados | "No se encontraron resultados para 'xyz'" |
| Error | "Error al cargar datos" |
## Estructura de Archivos
```
src/lib/
├── components/
│ └── search/
│ ├── SearchBox.svelte ← Input + lógica de búsqueda
│ ├── SearchResults.svelte ← Lista de resultados
│ └── SearchResultItem.svelte ← Item individual
├── services/
│ ├── supabase.js ← Cliente Supabase
│ └── search.js ← Inicialización Fuse.js
├── stores/
│ └── searchStore.js ← Estado: query, results, loading
├── utils/
│ └── normalize.js ← Normalización de texto
└── data/
└── index.js ← Carga y construcción del índice
```
## Estado de Implementación
| Clasificador | Estado | Tabla |
|--------------|--------|-------|
| Entidades | ✅ Implementado | `ppto.clas_institucional` |
| Objetos de gasto | ✅ Implementado | `ppto.clas_objetos` |
| Rubros | ✅ Implementado | `ppto.clas_rubros` |
| Fuente de financiamiento | ⏳ Pendiente | - |
| Geográfico | ⏳ Pendiente | - |
## Trabajo Futuro
### Índice pre-computado
Posibilidad de pre-computar el índice en servidor y servir como JSON estático para mejorar tiempo de carga inicial.
### Migración a servidor
Si el índice supera ~20,000 registros:
1. Habilitar extensión `pg_trgm` en PostgreSQL
2. Crear índices GIN en columnas de búsqueda
3. Cambiar de Fuse.js a queries con `similarity()` o `%` operator
4. El componente de UI se mantiene igual, solo cambia el servicio
---
*Documento de referencia para la implementación del buscador*
export const ssr = false;
This diff is collapsed. Click to expand it.