Frontend Hotfixes & Backend improvements #24
@@ -0,0 +1 @@
|
|||||||
|
{"pid":864463,"startedAt":1783550197179}
|
||||||
@@ -151,19 +151,52 @@ export async function getMedicineDetails(nregistro) {
|
|||||||
return JSON.parse(cachedData);
|
return JSON.parse(cachedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consultar la API de CIMA
|
// Intentar obtener de la API de CIMA (endpoint individual puede no existir)
|
||||||
console.log(`🌐 Fetching medicine details from CIMA: ${nregistro}`);
|
console.log(`🌐 Fetching medicine details from CIMA: ${nregistro}`);
|
||||||
const response = await axios.get(`${CIMA_API_BASE_URL}/medicamento/${nregistro}`, {
|
try {
|
||||||
|
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 || []
|
||||||
|
};
|
||||||
|
|
||||||
|
await redisClient.setEx(cacheKey, CACHE_TTL * 24, JSON.stringify(medicineDetails));
|
||||||
|
return medicineDetails;
|
||||||
|
}
|
||||||
|
} catch (apiError) {
|
||||||
|
console.log(`⚠️ Individual medicine endpoint failed, trying search fallback`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: buscar por nregistro usando el endpoint de búsqueda
|
||||||
|
const searchResponse = await axios.get(`${CIMA_API_BASE_URL}/medicamentos`, {
|
||||||
|
params: { nregistro },
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data) {
|
if (searchResponse.data?.resultados?.length > 0) {
|
||||||
const med = response.data;
|
const med = searchResponse.data.resultados[0];
|
||||||
const medicineDetails = {
|
const medicineDetails = {
|
||||||
id: med.nregistro,
|
id: med.nregistro,
|
||||||
nregistro: med.nregistro,
|
nregistro: med.nregistro,
|
||||||
name: med.nombre,
|
name: med.nombre,
|
||||||
active_ingredient: med.principiosActivos?.[0]?.nombre || med.vtm?.nombre || null,
|
active_ingredient: med.vtm?.nombre || null,
|
||||||
dosage: med.dosis || null,
|
dosage: med.dosis || null,
|
||||||
form: med.formaFarmaceutica?.nombre || null,
|
form: med.formaFarmaceutica?.nombre || null,
|
||||||
formSimplified: med.formaFarmaceuticaSimplificada?.nombre || null,
|
formSimplified: med.formaFarmaceuticaSimplificada?.nombre || null,
|
||||||
@@ -172,13 +205,10 @@ export async function getMedicineDetails(nregistro) {
|
|||||||
commercialized: med.comerc,
|
commercialized: med.comerc,
|
||||||
generic: med.generico,
|
generic: med.generico,
|
||||||
photos: med.fotos || [],
|
photos: med.fotos || [],
|
||||||
docs: med.docs || [],
|
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));
|
await redisClient.setEx(cacheKey, CACHE_TTL * 24, JSON.stringify(medicineDetails));
|
||||||
|
|
||||||
return medicineDetails;
|
return medicineDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import MapView, { Marker } from 'react-native-maps';
|
|||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { getPharmacies } from '../../services/pharmacies';
|
import { getPharmacies } from '../../services/pharmacies';
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner';
|
import { LoadingSpinner } from '../../components/LoadingSpinner';
|
||||||
import { colors, spacing } from '../../constants/theme';
|
import { colors, spacing, borderRadius } from '../../constants/theme';
|
||||||
import { Pharmacy } from '../../types';
|
import { Pharmacy } from '../../types';
|
||||||
|
|
||||||
export default function MapScreen() {
|
export default function MapScreen() {
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ const bgStyles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
opacity: 0.5,
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
overlay: {
|
overlay: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
backgroundColor: 'rgba(255, 252, 245, 0.48)',
|
backgroundColor: 'rgba(255, 252, 245, 0.2)',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,17 +56,18 @@ export default function MedicineDetailScreen() {
|
|||||||
return (
|
return (
|
||||||
<ScrollView style={styles.container}>
|
<ScrollView style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.name}>{medicine.nombre}</Text>
|
<Text style={styles.name}>{medicine.name}</Text>
|
||||||
<StockBadge stock={medicine.stock} />
|
{medicine.stock != null && <StockBadge stock={medicine.stock} />}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.infoSection}>
|
<View style={styles.infoSection}>
|
||||||
<InfoRow label="Principio activo" value={medicine.principioActivo} />
|
<InfoRow label="Principio activo" value={medicine.active_ingredient} />
|
||||||
<InfoRow label="Laboratorio" value={medicine.laboratorio} />
|
<InfoRow label="Laboratorio" value={medicine.laboratory} />
|
||||||
<InfoRow label="Forma farmacéutica" value={medicine.formaFarmaceutica} />
|
<InfoRow label="Forma farmacéutica" value={medicine.form} />
|
||||||
|
<InfoRow label="Dosificación" value={medicine.dosage} />
|
||||||
<InfoRow
|
<InfoRow
|
||||||
label="Precio"
|
label="Precio"
|
||||||
value={medicine.precio ? `${medicine.precio.toFixed(2)} €` : 'No disponible'}
|
value={medicine.precio != null ? `${medicine.precio.toFixed(2)} €` : 'No disponible'}
|
||||||
/>
|
/>
|
||||||
<InfoRow label="Registro" value={medicine.nregistro} />
|
<InfoRow label="Registro" value={medicine.nregistro} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -29,27 +29,27 @@ export function MedicineCard({ medicine }: MedicineCardProps) {
|
|||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={[styles.name, isTablet && styles.nameTablet]} numberOfLines={2}>
|
<Text style={[styles.name, isTablet && styles.nameTablet]} numberOfLines={2}>
|
||||||
{medicine.nombre}
|
{medicine.name}
|
||||||
</Text>
|
</Text>
|
||||||
<StockBadge stock={medicine.stock} />
|
{medicine.stock != null && <StockBadge stock={medicine.stock} />}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.principioActivo} numberOfLines={1}>
|
<Text style={styles.principioActivo} numberOfLines={1}>
|
||||||
{medicine.principioActivo}
|
{medicine.active_ingredient}{medicine.dosage ? ` - ${medicine.dosage}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<View style={styles.priceContainer}>
|
<View style={styles.priceContainer}>
|
||||||
<Ionicons name="pricetag" size={14} color={colors.textSecondary} />
|
<Ionicons name="pricetag" size={14} color={colors.textSecondary} />
|
||||||
<Text style={[styles.price, isTablet && styles.priceTablet]}>
|
<Text style={[styles.price, isTablet && styles.priceTablet]}>
|
||||||
{medicine.precio ? `${medicine.precio.toFixed(2)} €` : 'Sin precio'}
|
{medicine.precio != null ? `${medicine.precio.toFixed(2)} €` : 'Sin precio'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.labContainer}>
|
<View style={styles.labContainer}>
|
||||||
<Ionicons name="business" size={14} color={colors.textSecondary} />
|
<Ionicons name="business" size={14} color={colors.textSecondary} />
|
||||||
<Text style={[styles.laboratorio, isTablet && styles.laboratorioTablet]} numberOfLines={1}>
|
<Text style={[styles.laboratorio, isTablet && styles.laboratorioTablet]} numberOfLines={1}>
|
||||||
{medicine.laboratorio}
|
{medicine.laboratory}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ const ENV = {
|
|||||||
|
|
||||||
const environment = Constants.expoConfig?.extra?.environment || (__DEV__ ? 'development' : 'production');
|
const environment = Constants.expoConfig?.extra?.environment || (__DEV__ ? 'development' : 'production');
|
||||||
|
|
||||||
|
const envBaseUrl = process.env.EXPO_PUBLIC_API_URL;
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
API_BASE_URL: ENV[environment as keyof typeof ENV]?.API_BASE_URL || ENV.development.API_BASE_URL,
|
API_BASE_URL: envBaseUrl || ENV[environment as keyof typeof ENV]?.API_BASE_URL || ENV.development.API_BASE_URL,
|
||||||
SEARCH_DEBOUNCE_MS: 300,
|
SEARCH_DEBOUNCE_MS: 300,
|
||||||
CACHE_STALE_TIME: 5 * 60 * 1000, // 5 minutes
|
CACHE_STALE_TIME: 5 * 60 * 1000, // 5 minutes
|
||||||
CACHE_CACHE_TIME: 30 * 60 * 1000, // 30 minutes
|
CACHE_CACHE_TIME: 30 * 60 * 1000, // 30 minutes
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
export interface Medicine {
|
export interface Medicine {
|
||||||
|
id: string;
|
||||||
nregistro: string;
|
nregistro: string;
|
||||||
nombre: string;
|
name: string;
|
||||||
principioActivo: string;
|
active_ingredient: string;
|
||||||
laboratorio: string;
|
dosage: string;
|
||||||
formaFarmaceutica: string;
|
form: string;
|
||||||
precio: number | null;
|
formSimplified: string;
|
||||||
stock: number;
|
laboratory: string;
|
||||||
disponibilidad: 'disponible' | 'sin_stock' | 'bajo_stock';
|
prescription: string;
|
||||||
|
commercialized: boolean;
|
||||||
|
generic: boolean;
|
||||||
|
photos: string[];
|
||||||
|
docs: { tipo: number; url: string; urlHtml?: string; secc: boolean; fecha: number }[];
|
||||||
|
// Optional pharmacy-specific fields (only present in pharmacy_medicines joins)
|
||||||
|
precio?: number | null;
|
||||||
|
stock?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Pharmacy {
|
export interface Pharmacy {
|
||||||
|
|||||||
Reference in New Issue
Block a user