187 lines
5.8 KiB
JavaScript
187 lines
5.8 KiB
JavaScript
import axios from 'axios';
|
|
import redisClient from './redis-client.js';
|
|
|
|
const CIMA_API_BASE_URL = 'https://cima.aemps.es/cima/rest';
|
|
const CACHE_TTL = 3600; // 1 hora en segundos
|
|
|
|
/**
|
|
* CIMA's nombre filter is prefix-oriented; narrow to rows that contain every
|
|
* search term in the commercial name or active ingredient (full-word style).
|
|
*/
|
|
function filterMedicinesByFullQuery(medicines, searchTerm) {
|
|
const terms = searchTerm
|
|
.trim()
|
|
.toLowerCase()
|
|
.split(/\s+/)
|
|
.filter(Boolean);
|
|
if (terms.length === 0) return medicines;
|
|
return medicines.filter((m) => {
|
|
const hay = `${m.name || ''} ${m.active_ingredient || ''}`.toLowerCase();
|
|
return terms.every((term) => hay.includes(term));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Busca medicamentos en la API de CIMA con caché de Redis
|
|
* @param {string} query - Término de búsqueda
|
|
* @returns {Promise<Array>} - Lista de medicamentos encontrados
|
|
*/
|
|
export async function searchMedicines(query) {
|
|
if (!query || query.trim().length < 2) {
|
|
return [];
|
|
}
|
|
|
|
const searchTerm = query.trim().toLowerCase();
|
|
const cacheKey = `medicines:search:v2:${searchTerm}`;
|
|
|
|
try {
|
|
// Intentar obtener del caché
|
|
const cachedData = await redisClient.get(cacheKey);
|
|
|
|
if (cachedData) {
|
|
console.log(`📦 Cache hit for: ${searchTerm}`);
|
|
return JSON.parse(cachedData);
|
|
}
|
|
|
|
// Si no está en caché, consultar la API de CIMA
|
|
console.log(`🌐 Fetching from CIMA API: ${searchTerm}`);
|
|
const response = await axios.get(`${CIMA_API_BASE_URL}/medicamentos`, {
|
|
params: {
|
|
nombre: searchTerm
|
|
},
|
|
timeout: 5000
|
|
});
|
|
|
|
if (response.data && response.data.resultados) {
|
|
// Transformar los datos de CIMA a nuestro formato
|
|
const medicines = response.data.resultados.map(med => ({
|
|
id: med.nregistro,
|
|
nregistro: med.nregistro,
|
|
name: med.nombre,
|
|
active_ingredient: med.vtm?.nombre || null,
|
|
dosage: med.dosis || null,
|
|
form: med.formaFarmaceutica?.nombre || null,
|
|
formSimplified: med.formaFarmaceuticaSimplificada?.nombre || null,
|
|
laboratory: med.labtitular,
|
|
prescription: med.cpresc,
|
|
commercialized: med.comerc,
|
|
generic: med.generico,
|
|
photos: med.fotos || [],
|
|
docs: med.docs || []
|
|
}));
|
|
|
|
const filtered = filterMedicinesByFullQuery(medicines, searchTerm);
|
|
|
|
// Guardar en caché
|
|
await redisClient.setEx(cacheKey, CACHE_TTL, JSON.stringify(filtered));
|
|
|
|
console.log(`✅ Cached ${filtered.length} medicines for: ${searchTerm}`);
|
|
return filtered;
|
|
}
|
|
|
|
return [];
|
|
} catch (error) {
|
|
console.error('Error searching medicines from CIMA:', error.message);
|
|
|
|
// Si falla, intentar devolver datos cacheados aunque hayan expirado
|
|
try {
|
|
const staleData = await redisClient.get(cacheKey);
|
|
if (staleData) {
|
|
console.log('⚠️ Returning stale cache data due to API error');
|
|
return JSON.parse(staleData);
|
|
}
|
|
} catch (cacheError) {
|
|
console.error('Cache fallback also failed:', cacheError);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene detalles de un medicamento específico por su número de registro
|
|
* @param {string} nregistro - Número de registro del medicamento
|
|
* @returns {Promise<Object|null>} - Datos del medicamento
|
|
*/
|
|
export async function getMedicineDetails(nregistro) {
|
|
const cacheKey = `medicine:${nregistro}`;
|
|
|
|
try {
|
|
// Intentar obtener del caché
|
|
const cachedData = await redisClient.get(cacheKey);
|
|
|
|
if (cachedData) {
|
|
console.log(`📦 Cache hit for medicine: ${nregistro}`);
|
|
return JSON.parse(cachedData);
|
|
}
|
|
|
|
// Consultar la API de CIMA
|
|
console.log(`🌐 Fetching medicine details from CIMA: ${nregistro}`);
|
|
const response = await axios.get(`${CIMA_API_BASE_URL}/medicamento/${nregistro}`, {
|
|
timeout: 5000
|
|
});
|
|
|
|
if (response.data) {
|
|
const med = response.data;
|
|
const medicineDetails = {
|
|
id: med.nregistro,
|
|
nregistro: med.nregistro,
|
|
name: med.nombre,
|
|
active_ingredient: med.principiosActivos?.[0]?.nombre || med.vtm?.nombre || null,
|
|
dosage: med.dosis || null,
|
|
form: med.formaFarmaceutica?.nombre || null,
|
|
formSimplified: med.formaFarmaceuticaSimplificada?.nombre || null,
|
|
laboratory: med.labtitular,
|
|
prescription: med.cpresc,
|
|
commercialized: med.comerc,
|
|
generic: med.generico,
|
|
photos: med.fotos || [],
|
|
docs: med.docs || [],
|
|
presentations: med.presentaciones || []
|
|
};
|
|
|
|
// Guardar en caché (TTL más largo para detalles específicos)
|
|
await redisClient.setEx(cacheKey, CACHE_TTL * 24, JSON.stringify(medicineDetails));
|
|
|
|
return medicineDetails;
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error(`Error fetching medicine ${nregistro} from CIMA:`, error.message);
|
|
|
|
// Intentar devolver datos cacheados aunque hayan expirado
|
|
try {
|
|
const staleData = await redisClient.get(cacheKey);
|
|
if (staleData) {
|
|
console.log('⚠️ Returning stale cache data due to API error');
|
|
return JSON.parse(staleData);
|
|
}
|
|
} catch (cacheError) {
|
|
console.error('Cache fallback also failed:', cacheError);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpia el caché de búsquedas (útil para testing o mantenimiento)
|
|
* @param {string} pattern - Patrón de claves a eliminar (ej: 'medicines:search:*')
|
|
* @returns {Promise<number>} - Número de claves eliminadas
|
|
*/
|
|
export async function clearCache(pattern = 'medicines:*') {
|
|
try {
|
|
const keys = await redisClient.keys(pattern);
|
|
if (keys.length > 0) {
|
|
await redisClient.del(keys);
|
|
console.log(`🗑️ Cleared ${keys.length} cache entries`);
|
|
return keys.length;
|
|
}
|
|
return 0;
|
|
} catch (error) {
|
|
console.error('Error clearing cache:', error);
|
|
return 0;
|
|
}
|
|
}
|