Lección 14 de 20 · Lectura y práctica
Práctica: App de fotos con API y almacenamiento
Concepto claveEn el desarrollo de aplicaciones móviles modernas, la integración de APIs externas con funcionalidades nativas del dispositivo es fundamental para crear experiencias ricas y útiles. Imagina que estás construyendo.
Del concepto a la práctica
Taller de esta lección
La práctica combina tres resultados que no deben confundirse: una imagen elegida localmente, un registro persistido y una subida remota. Copia primero el archivo a una ubicación duradera y guarda la referencia local y muestra estados separados para seleccionar y enviar. En un laboratorio local, la API puede simular una respuesta con un identificador, sin afirmar que hubo una subida real.
Ejemplo resuelto
Entrada: URI local file:///foto-a.jpg, título Pizarra. Registro persistido {id:'f1', uri:'file:///foto-a.jpg', title:'Pizarra'}. La API simulada responde {remoteId:'r7'} y el estado pasa a simulated_uploaded.
Pruébalo
Solicita acceso autorizado a galería o cámara.
Valida que exista URI.
Persiste el registro local.
Simula la respuesta remota y actualiza el estado.
Error frecuente: marcar subida antes de recibir respuesta. Corrige usando uploading y solo después simulated_uploaded o error.
Ejercicio: procesa una selección cancelada.
Comprueba tu resultado
no se crea registro, no se llama a la API y la interfaz conserva el estado listo para intentar de nuevo.
Archivo duradero y tres resultados de envío
file:///foto-a.jpg es una URI ficticia para simular estados, no un archivo entregado. La selección real usa result.assets[0].uri tras comprobar canceled. Cancelar conserva el preview anterior, crea cero registros y realiza cero envíos. Para galería actual usa mediaTypes:['images']; para cámara solicita su permiso contextual. No conviertas PNG/HEIC a JPEG cambiando el nombre.
Antes de guardar metadata para reinicio, copia el archivo recibido desde caché a una ruta propia con File/Paths de expo-file-system; por ejemplo await new File(uri).copy(new File(Paths.document,'f1.jpg')) únicamente si el archivo realmente es JPEG. Persiste la URI del destino después de una copia correcta. Al reintentar conserva id f1 y archivo local; la lógica no debe crear otro registro por cada toque.
async function enviarFotoPrueba(registro,modo){
if(!registro||registro.id!=='f1'||!registro.title?.trim()||!registro.uri) throw new Error('Registro inválido');
if(modo==='red') throw new Error('Red simulada no disponible');
if(modo==='rechazo') return {status:'error',reason:'Formato rechazado'};
if(modo==='aprobacion') return {status:'simulated_uploaded',remoteId:'r7'};
throw new Error('Modo inválido');
}Prueba aprobación/rechazo/red desde el mismo fixture y conserva archivo ante fallos. Solo la primera acredita un resultado simulado, no subida remota real. Para FormData real permite que el transporte genere boundary, aporta el MIME verdadero y valida archivo en servidor; credenciales privadas de almacenamiento no van en el cliente. Archivos.

Mapa de prácticaEl navegador envía una solicitud a la API. El servidor valida la operación y consulta los datos; después devuelve una respuesta que el navegador puede mostrar. La base de datos se comunica con el servidor y no directamente con el navegador.
Ver la infografía en tamaño completo ↗Concepto clave
En el desarrollo de aplicaciones móviles modernas, la integración de APIs externas con funcionalidades nativas del dispositivo es fundamental para crear experiencias ricas y útiles. Imagina que estás construyendo una app de fotos: necesitas acceder a la cámara del teléfono (funcionalidad nativa) y luego subir esas imágenes a un servidor (API externa). Esta lección te enseñará cómo combinar ambas cosas usando React Native con Expo.
Piensa en esto como un restaurante: la cocina (tu app) tiene ingredientes locales (fotos del dispositivo) pero también necesita pedir suministros especiales (datos de un servidor). Expo actúa como el chef que sabe cómo manejar ambos flujos de trabajo de manera eficiente, usando herramientas como expo-image-picker para la cámara y fetch o axios para las APIs.
Cómo funciona en la práctica
Vamos a construir una app que permite al usuario tomar una foto con la cámara de su dispositivo, mostrar una vista previa, y luego subirla a un servidor usando una API REST. El proceso sigue estos pasos:
- Solicitar permisos para acceder a la cámara y la galería del dispositivo.
- Usar expo-image-picker para abrir la interfaz de cámara y capturar una imagen.
- Mostrar la imagen seleccionada en un componente Image de React Native.
- Crear un formulario con datos adicionales (como un título) usando useState.
- Enviar la imagen y los datos a un endpoint de API mediante una petición POST multipart/form-data.
- Manejar la respuesta del servidor y actualizar el estado de la app.
Este flujo es común en apps sociales, herramientas de inventario, o cualquier aplicación que requiera contenido generado por el usuario.
Código en acción
Aquí tienes un ejemplo básico de cómo implementar la captura de una foto:
import React, { useState } from 'react';
import { Button, Image, View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function PhotoCapture() {
const [image, setImage] = useState(null);
const pickImage = async () => {
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled) {
setImage(result.assets[0].uri);
}
};
return (
{image && }
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
image: { width: 200, height: 200, marginTop: 20 },
});Ahora, vamos a mejorar este código añadiendo la funcionalidad de subir la foto a una API. Observa cómo refactorizamos para manejar el envío:
import React, { useState } from 'react';
import { Button, Image, View, TextInput, Alert, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function PhotoUploader() {
const [image, setImage] = useState(null);
const [title, setTitle] = useState('');
const [uploading, setUploading] = useState(false);
const pickImage = async () => {
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled) {
setImage(result.assets[0]);
}
};
const uploadImage = async () => {
if (!image || !title.trim()) {
Alert.alert('Error', 'Por favor, toma una foto y añade un título');
return;
}
setUploading(true);
const formData = new FormData();
formData.append('title', title);
formData.append('photo', {
uri: image.uri,
type: 'image/jpeg',
name: 'photo.jpg',
});
try {
const response = await fetch('https://api.ejemplo.com/upload', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
const data = await response.json();
if (response.ok) {
Alert.alert('Éxito', 'Foto subida correctamente');
setImage(null);
setTitle('');
} else {
Alert.alert('Error', data.message || 'Falló la subida');
}
} catch (error) {
Alert.alert('Error', 'No se pudo conectar al servidor');
} finally {
setUploading(false);
}
};
return (
{image && }
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, alignItems: 'center' },
image: { width: 200, height: 200, marginVertical: 20 },
input: { borderWidth: 1, borderColor: '#ccc', padding: 10, width: '100%', marginBottom: 20 },
});Errores comunes
- Olvidar solicitar permisos: En iOS y Android, necesitas configurar permisos en app.json y solicitarlos en tiempo de ejecución. Usa ImagePicker.requestCameraPermissionsAsync() antes de abrir la cámara.
- Manejo incorrecto de FormData: Al subir archivos, asegúrate de que el objeto en formData.append() tenga las propiedades uri, type, y name. Sin esto, el servidor puede rechazar la petición.
- No validar entradas del usuario: Siempre verifica que la imagen y campos como el título estén presentes antes de enviar, para evitar errores 400 del servidor.
- Ignorar estados de carga: Deshabilita botones durante la subida con un estado uploading para prevenir múltiples envíos y mejorar la experiencia de usuario.
- Manejo pobre de errores de red: Envuelve las peticiones fetch en try-catch para capturar fallos de conexión y mostrar mensajes claros al usuario.
Checklist de dominio
- Sé cómo solicitar y verificar permisos para cámara y galería en Expo.
- Puedo capturar una foto usando expo-image-picker y mostrarla en pantalla.
- Entiendo cómo crear un FormData para subir archivos a una API REST.
- Sé manejar estados de carga y error durante las peticiones de red.
- Puedo integrar entradas de usuario (como TextInput) con la funcionalidad de subida.
- Comprendo la importancia de validar datos antes de enviarlos al servidor.
- Soy capaz de probar la app en un dispositivo real o emulador para verificar el flujo completo.
Construye una app de galería de fotos con subida a Cloudinary
En este ejercicio, crearás una app que permite a los usuarios tomar fotos, añadir descripciones, y subirlas a Cloudinary (un servicio de almacenamiento en la nube). Sigue estos pasos:
- Configura un proyecto nuevo de Expo con
npx create-expo-app PhotoGallery. - Instala las dependencias necesarias:
expo-image-pickeryaxios. - Crea una cuenta gratuita en Cloudinary y obtén tu cloud name y upload preset.
- Implementa un componente que solicite permisos de cámara, capture una foto, y muestre una vista previa.
- Añade un campo TextInput para que el usuario ingrese una descripción de la foto.
- Configura una función que suba la foto a Cloudinary usando axios, enviando la imagen como FormData.
- Muestra un indicador de carga durante la subida y un mensaje de éxito o error al finalizar.
- Prueba la app en tu dispositivo o emulador, verificando que la foto se suba correctamente y aparezca en tu dashboard de Cloudinary.
Pistas
- Recuerda configurar los permisos en app.json para iOS y Android antes de probar en dispositivo.
- Cloudinary requiere enviar el archivo en un campo llamado 'file' en el FormData, consulta su documentación para detalles.
- Usa el estado uploading para deshabilitar botones y mostrar un ActivityIndicator durante la subida.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.
Sigue con React Native y Expo: Interfaces, Estado y APIs de Dispositivo
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.