Documentation Index
Fetch the complete documentation index at: https://mintlify.com/RubenDarioGuerreroNeira/Ecosistema-IA-Colombia/llms.txt
Use this file to discover all available pages before exploring further.
Salud IA Bot covers four Colombian regions with dedicated health-provider services. Each service ingests its region’s open-data file (XML or SQLite), builds an in-memory or TypeORM-backed index, and returns zero-hallucination results directly — no LLM call is made for provider lookups. All services share the same normalizeString() utility for accent-insensitive comparison.
Region Coverage
| Service | Data source | Backend | Search capabilities |
|---|
CaliHealthService | SERVICIOS_OFERTADOS_RED_DE_SALUD_DEL_CENTRO_ESE_POR_SEDE_CALI.xml | In-memory + TTL cache | Urgency, complexity, service type, sede name, group/category |
YopalHealthService | Centros_de_salud_Yopal._.xml | In-memory + TTL cache | Provider name, service type, GPS proximity (Haversine), category, schedule |
AntioquiaHealthService | Prestadores_de_Salud_Departamento_de_Antioquia.xml -> SQLite | TypeORM + TTL cache | Municipality, provider name, sede, NIT, código de habilitación |
BoyacaHealthService | servicios_salud_boyaca.xml -> SQLite | TypeORM | Municipality, razon social, sede name, NIT, código prestador |
CaliHealthService
CaliHealthService loads the Red de Salud del Centro ESE dataset for Cali. It parses XML into an array of CaliHealthProvider records, caches query results for 5 minutes, and processes natural-language queries through an 8-step intent pipeline.
CaliHealthProvider interface
export interface CaliHealthProvider {
complejidad?: string; // 'alta' | 'media' | 'baja'
sede?: string; // Facility/branch name
grupo?: string; // Service group/category
servicio?: string; // Specific service name
direccion?: string;
geolocalizacion?: string;
departamento?: string;
ciudad?: string;
telefono?: string;
extension?: string;
}
Zero-hallucination bypass
processCaliQuery() returns structured data directly from the in-memory provider list. The LLM is never invoked for Cali lookups.
processCaliQuery(text: string) — intent pipeline
Processing is ordered: the first matching branch returns immediately.
| Step | Condition | Method | Example query |
|---|
| 1 | isKnowledgeQuery() | getAvailableQuestions() | "¿Qué sabes de Cali?" |
| 2 | urgencia / emergencia / 24 horas in text | getEmergencyServices() | "Urgencias en Cali" |
| 3 | categorias / grupos / disponible in text | getGruposDisponibles() | "Categorías de servicios en Cali" |
| 4 | estadistica / cuantos / resumen / total | getKnowledgeSummary() | "¿Cuántas sedes tiene la red de salud?" |
| 5 | Sede name found in text | formatSedeDetails() | "Servicios de la Sede Alfonso López" |
| 6 | hospital / clinica keywords | Provider sede/grupo filter | "Hospitales en Cali" |
| 7 | Known service keyword match | searchByService() | "Odontología en Cali" |
| 8 | Fallback | searchProviders() | Any unmatched query |
Key methods
| Method | Returns | Description |
|---|
searchProviders(query) | CaliHealthProvider[] | Token-based search across sede, servicio, grupo, dirección |
getProvidersByGroup(group) | CaliHealthProvider[] | Filters by service group/category |
getProvidersByComplejidad(complejidad) | CaliHealthProvider[] | Filters by complexity level |
searchByService(serviceName) | CaliHealthProvider[] | Filters by exact service name inclusion |
getEmergencyServices() | CaliHealthProvider[] | Providers with “urgencia” or “emergencia” in grupo/servicio |
getProviderDetails(sedeName) | {sede, direccion, telefono, servicios[], totalServicios} | Full sede profile |
getStatsByCategory() | {labels: string[], data: number[]} | Category distribution for bar chart |
getGruposDisponibles() | {grupo: string, count: number}[] | All categories with service count |
getUniqueSedes() | string[] | Alphabetical list of all facility names |
Normalization
private normalizeString(value?: string): string {
return (value || '')
.toString()
.normalize('NFD')
.replace(/[-\u0300-\u036f]/g, '') // remove accents and dashes
.toLowerCase()
.trim();
}
Stop-word filtering strips common Spanish articles, prepositions, and domain words (salud, servicios, hospital, etc.) before token matching to reduce false positives.
YopalHealthService
YopalHealthService covers Yopal, Casanare. Its XML data includes GPS coordinates for most providers, enabling proximity search. The service also includes Jaro-Winkler fuzzy matching for provider name lookups.
YopalHealthProvider interface
export interface YopalHealthProvider {
departamento?: string;
municipio?: string;
orden?: string;
sector?: string;
idioma?: string;
entidad_2?: string; // Provider name
gerente?: string;
direccion?: string;
telefono?: string;
correo_electronico?: string;
latitud?: string;
longitud?: string;
}
findNearby(lat, lon, radiusKm = 5)
Haversine-based proximity search. Returns providers within radiusKm kilometres, sorted by ascending distance.
findNearby(lat: number, lon: number, radiusKm: number = 5) {
const cacheKey = `nearby_${lat}_${lon}_${radiusKm}`;
const cached = this.getCached(cacheKey);
if (cached) return cached;
const providersWithCoords = this.getProvidersWithCoords();
const result = providersWithCoords
.map((p) => ({
...p,
distance: this.calculateDistance(lat, lon,
p.normalizedCoords.lat, p.normalizedCoords.lon),
}))
.filter((p) => p.distance <= radiusKm)
.sort((a, b) => a.distance - b.distance);
this.setCached(cacheKey, result);
return result;
}
Coordinate normalization handles raw integer coordinates (e.g. 5319820 → 5.319820) and validates that values fall within Yopal/Casanare’s geographic bounding box (lat 4–6, lon −70 to −74).
GPS proximity flow
When a user sends "centros de salud cerca de mí", BotUpdate detects the proximity intent via isNearbyLocationQuery(), sends a Telegram keyboard with request_location: true, and then calls YopalHealthService.findNearby(lat, lon) upon receiving the @On('location') event.
Key methods
| Method | Returns | Description |
|---|
searchByService(serviceKeyword) | YopalHealthProvider[] | Matches name and provider category |
getProvidersByCategory(category) | YopalHealthProvider[] | Category classification via keyword mapping |
getEmergencyProviders() | YopalHealthProvider[] | Providers with urgency/24h keywords |
getProvidersBySchedule(schedule) | YopalHealthProvider[] | Filter by '24h' or 'urgent' |
suggestProvidersForCondition(condition) | YopalHealthProvider[] | Maps conditions (diabetes, fractura…) to specialist types |
findByIdentifier(query) | YopalHealthProvider[] | Token + fuzzy (Jaro-Winkler >= 0.8) search |
getTerritoryStats() | stats object | Totals by category, road type, and geo coverage |
answerNaturalQuestion(question) | Promise<{content, found}> | Full NL intent router for Yopal queries |
Provider categories
const mapping = {
EPS: ['CAPRESOCA', 'COOMEVA', 'MEDIMAS', 'SANITAS', 'NUEVA EPS', 'COOSALUD'],
'HOSPITAL/CLINICA': ['HOSPITAL', 'CLINICA', 'CENTRO MEDICO', 'CAIMED'],
ODONTOLOGIA: ['ODONTO', 'DENTAL', 'DENTISALUD'],
LABORATORIO: ['LABORATORIO', 'FAMELAB'],
'RADIOLOGIA/DIAGNOSTICO': ['RADIOLOG', 'RX', 'ESCANOGRAFIA', 'TOMOGRAFO'],
'OPTICA/OFTALMOLOGIA': ['OPTICA', 'OFTALMO', 'OPTISALUD'],
REHABILITACION: ['REHABILITAR', 'KAIROS', 'FISIOTERAPIA'],
'TRANSPORTE/AMBULANCIA': ['AMBULANCIA', 'TEVA', 'AEREA Y TERRESTRE'],
};
AntioquiaHealthService
AntioquiaHealthService queries the antioquia_provider SQLite table via TypeORM. It is the largest dataset and uses SQL LIKE queries with plural stemming for robust results.
processAntioquiaQuery(text: string) — intent pipeline
| Step | Condition | Action |
|---|
| 1 | isKnowledgeQuery() | Return getAvailableQuestions() |
| 2 | municipios / ciudades in text | Return distinct municipality list |
| 3 | codigo / habilitacion / nit or digit-only query | findByIdentifier() |
| 4 | Fallback | searchProviders() |
searchProviders(query, limit = 100)
Tokenizes the query, applies plural stemming (-es / -s suffix removal for tokens > 4–5 chars), and runs a multi-field TypeORM LIKE query against municipio, nombreprestador, nombre_sede, and gerente for each token. Results are filtered to exclude non-Antioquia rows (Yopal, Casanare, Arauca, etc.).
queryBuilder.andWhere(
`(LOWER(p.municipio) LIKE '%' || LOWER(:tok0) || '%'
OR LOWER(p.nombreprestador) LIKE '%' || LOWER(:tok0) || '%'
OR LOWER(p.nombre_sede) LIKE '%' || LOWER(:tok0) || '%'
OR LOWER(p.gerente) LIKE '%' || LOWER(:tok0) || '%')`,
{ tok0: token },
);
findByIdentifier(query)
Tries código de habilitación → NIT → full text search in that order. Results are deduplicated by codigohabilitacion + nombreprestador + nombre_sede.
BoyacaHealthService
BoyacaHealthService is a focused SQLite-backed service for Boyacá health providers using the boyaca_provider entity. It shares the same token-based search pattern as AntioquiaHealthService.
Key methods
| Method | Returns | Description |
|---|
searchProviders(query, limit) | Promise<BoyacaProvider[]> | Token LIKE search across municipio, razon_social, nombre_de_sede, gerente |
findByIdentifier(query) | Promise<BoyacaProvider[]> | codigo_prestador -> codigo_habilitacion -> NIT -> text fallback |
getMunicipios() | Promise<string[]> | Distinct municipalities with providers |
getHospitalCount() | Promise<number> | Count of rows with “HOSPITAL” in razon_social or nombre_de_sede |
getKnowledgeSummary() | string | Short capability description for onboarding |
Both Antioquia and Boyacá services use a shared normalizeString() + STOPWORDS filter from src/shared/health-utils:
protected getSignificantTokens(query: string, maxTokens = 10): string[] {
return normalizeString(query)
.split(/\s+/)
.filter(t => t.length >= 3 && !STOPWORDS.has(t))
.slice(0, maxTokens);
}
Accent Normalization
All regional services apply Unicode NFD decomposition followed by combining-character removal before any string comparison:
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
This ensures "Medellín", "Medellin", and "MEDELLIN" all resolve identically — critical for the Colombian geographic names that appear in both user messages and dataset fields.
YopalHealthService additionally applies a cleanEncoding() pass that corrects Latin-1 / UTF-8 mojibake artifacts (ñ → ñ, ó → ó, etc.) introduced by some Socrata XML exports.
All four regional services implement a 5-minute TTL cache keyed on the normalized query string. Repeated identical queries within that window are served from memory without touching the XML or SQLite layer.