Skip to main content

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

ServiceData sourceBackendSearch capabilities
CaliHealthServiceSERVICIOS_OFERTADOS_RED_DE_SALUD_DEL_CENTRO_ESE_POR_SEDE_CALI.xmlIn-memory + TTL cacheUrgency, complexity, service type, sede name, group/category
YopalHealthServiceCentros_de_salud_Yopal._.xmlIn-memory + TTL cacheProvider name, service type, GPS proximity (Haversine), category, schedule
AntioquiaHealthServicePrestadores_de_Salud_Departamento_de_Antioquia.xml -> SQLiteTypeORM + TTL cacheMunicipality, provider name, sede, NIT, código de habilitación
BoyacaHealthServiceservicios_salud_boyaca.xml -> SQLiteTypeORMMunicipality, 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.
StepConditionMethodExample query
1isKnowledgeQuery()getAvailableQuestions()"¿Qué sabes de Cali?"
2urgencia / emergencia / 24 horas in textgetEmergencyServices()"Urgencias en Cali"
3categorias / grupos / disponible in textgetGruposDisponibles()"Categorías de servicios en Cali"
4estadistica / cuantos / resumen / totalgetKnowledgeSummary()"¿Cuántas sedes tiene la red de salud?"
5Sede name found in textformatSedeDetails()"Servicios de la Sede Alfonso López"
6hospital / clinica keywordsProvider sede/grupo filter"Hospitales en Cali"
7Known service keyword matchsearchByService()"Odontología en Cali"
8FallbacksearchProviders()Any unmatched query

Key methods

MethodReturnsDescription
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. 53198205.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

MethodReturnsDescription
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 objectTotals 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

StepConditionAction
1isKnowledgeQuery()Return getAvailableQuestions()
2municipios / ciudades in textReturn distinct municipality list
3codigo / habilitacion / nit or digit-only queryfindByIdentifier()
4FallbacksearchProviders()

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ónNIT → 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

MethodReturnsDescription
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()stringShort capability description for onboarding

Significant token extraction

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.

Build docs developers (and LLMs) love