React Native con Expo - App móvil de inventario
"Antes de programar un CRUD completo, construiremos la base visual y la estructura inicial de la aplicación."
En esta serie de guías construiremos una aplicación móvil de inventario usando React Native con Expo.
La aplicación final permitirá administrar dos elementos principales: categorías y productos.
Servirán para clasificar los productos. Por ejemplo: tecnología, oficina, accesorios, limpieza o alimentos.
CRUD 1Serán los artículos del inventario. Cada producto deberá pertenecer a una categoría.
CRUD 2Al finalizar esta guía, tendrás creada la primera versión visual de la aplicación. Esta pantalla funcionará como el inicio del sistema de inventario.
HomeScreen.useState para manejar datos temporales.Administra categorías y productos.
$850.00 - Stock: 8
Tecnología$18.50 - Stock: 25
Accesorios$95.00 - Stock: 4
OficinaLa aplicación se llamará Inventario App. Su propósito será administrar productos y categorías desde una app móvil.
Un producto estará relacionado con una categoría. Por ejemplo:
| Categoría | Producto | Ejemplo |
|---|---|---|
| Tecnología | Laptop Dell | La laptop pertenece a la categoría Tecnología. |
| Accesorios | Mouse inalámbrico | El mouse pertenece a la categoría Accesorios. |
| Oficina | Silla ejecutiva | La silla pertenece a la categoría Oficina. |
Para esta serie de guías asumiremos que ya existen endpoints para realizar las operaciones de categorías y productos.
GET /categorias
POST /categorias
PUT /categorias/:id
DELETE /categorias/:id
GET /productos
POST /productos
PUT /productos/:id
DELETE /productos/:id
En primer lugar, crearemos un proyecto nuevo de Expo. Expo nos permite trabajar con React Native de forma más sencilla, probar la app en el navegador, emulador o dispositivo físico, y desarrollar sin configurar todo manualmente desde cero.
Abre una terminal en la carpeta donde guardarás tus proyectos y escribe:
npx create-expo-app@latest inventario-app --template blank
Con este comando se creará una carpeta llamada inventario-app. Dentro estará el proyecto inicial.
cd inventario-app
Para evitar problemas con la parte superior de la pantalla en algunos dispositivos,
usaremos react-native-safe-area-context.
npx expo install react-native-safe-area-context
npx expo start
Al ejecutar este comando, Expo mostrará opciones para abrir la app en Android, iOS, web o mediante Expo Go.
Ahora vamos a ordenar el proyecto. Aunque al inicio la aplicación será pequeña, es importante organizarla desde el principio.
La carpeta screens guardará las pantallas principales. La carpeta components guardará piezas reutilizables de la interfaz.
mkdir src
mkdir src/screens
mkdir src/components
El archivo App.js es el punto de entrada principal de la aplicación.
Al crear un proyecto nuevo, Expo coloca código de ejemplo dentro de este archivo. Nosotros lo reemplazaremos poco a poco.
import { StatusBar } from 'expo-status-bar';
import { Text, View } from 'react-native';
export default function App() {
return (
<View>
<Text>Inventario App</Text>
<StatusBar style="auto" />
</View>
);
}
Este código solamente muestra un texto en pantalla. Todavía no tiene diseño. Lo usamos únicamente para comprobar que la app funciona.
View funciona como un contenedor.Text permite mostrar texto en pantalla.StatusBar controla la barra superior del dispositivo.Más adelante tendremos varias pantallas (inicio, categorías, productos, perfil, etc.). Por eso, en lugar de colocar todo dentro de App.js, crearemos pantallas separadas como HomeScreen.js.
Vamos a cambiar App.js paso a paso.
Paso 1: Importar HomeScreen
Agrega una nueva línea de importación:
import { StatusBar } from 'expo-status-bar';
import HomeScreen from './src/screens/HomeScreen';
Esta línea importa el componente HomeScreen que crearemos después.
Paso 2: Reemplazar el contenido de App
Ahora, en la función App, reemplaza el contenido del return por:
import { StatusBar } from 'expo-status-bar';
import HomeScreen from './src/screens/HomeScreen';
export default function App() {
return (
<>
<HomeScreen />
<StatusBar style="light" />
</>
);
}
Aquí:
<HomeScreen /> importa la pantalla principal.<StatusBar style="light" /> configura la barra de estado del dispositivo.<></> (Fragment) es un contenedor invisible que agrupa estos elementos.Dentro de la carpeta src/screens, crea un archivo llamado:
Primero escribiremos una pantalla sencilla para comprobar que todo está conectado correctamente.
La pantalla tendrá tres partes importantes:
SafeAreaView: Evita que el contenido se superponga con notches o barras del dispositivo.View: Un contenedor básico.Text: El texto que mostraremos.import { View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function HomeScreen() {
return (
<SafeAreaView style={styles.container}>
<View>
<Text>Inventario App</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
});
En este punto ya deberías ver el texto Inventario App en la pantalla.
Inventario App
Indica que el contenedor debe ocupar todo el espacio disponible de la pantalla.
Como la pantalla tendrá varias tarjetas y listas de productos, agregaremos un
ScrollView. Esto permitirá desplazarse si el contenido no cabe completo
en la pantalla.
Paso 2.1: Importar ScrollView
Modifica la primera línea para agregar ScrollView:
import { ScrollView, View, Text, StyleSheet } from 'react-native';
Paso 2.2: Envolver el contenido en ScrollView
import { ScrollView, View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function HomeScreen() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.content}>
<Text>Inventario App</Text>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
content: {
padding: 20,
},
});
Ahora el contenido está dentro de un ScrollView. Nota que usamos
contentContainerStyle={styles.content}. Este atributo nos permite
aplicar estilos al contenido interno del ScrollView (agregar espaciado, etc.).
Reemplazaremos el texto simple por un encabezado visual. Vamos a construirlo gradualmente.
Paso 3.1: Crear el contenedor del encabezado
Reemplaza el texto "Inventario App" por esto:
<View style={styles.header}>
<Text>Inventario App</Text>
</View>
En este punto, tendrás una caja azul con el texto.
Paso 3.2: Agregar el "kicker" (etiqueta pequeña)
Ahora agrega un texto antes del título principal:
<View style={styles.header}>
<Text style={styles.kicker}>Sistema de inventario</Text>
<Text>Inventario App</Text>
</View>
El "kicker" es un pequeño texto superior que describe el contexto.
Paso 3.3: Estilizar el título principal
Dale estilos especiales al título:
<View style={styles.header}>
<Text style={styles.kicker}>Sistema de inventario</Text>
<Text style={styles.title}>Inventario App</Text>
</View>
Paso 3.4: Agregar el subtítulo descriptivo
Finalmente, agrega un subtítulo debajo del título:
<View style={styles.header}>
<Text style={styles.kicker}>Sistema de inventario</Text>
<Text style={styles.title}>Inventario App</Text>
<Text style={styles.subtitle}>
Administra categorías y productos desde una aplicación móvil.
</Text>
</View>
Agregaremos los estilos en el mismo orden que agregamos los elementos.
Paso 4.1: Estilo del contenedor principal
const styles = StyleSheet.create({
container: { ... },
content: { ... },
header: {
backgroundColor: '#1a5276',
padding: 22,
borderRadius: 22,
marginBottom: 20,
},
});
Esto crea una caja azul oscura con bordes redondeados.
Paso 4.2: Estilos del kicker y título
const styles = StyleSheet.create({
...
header: { ... },
kicker: {
color: '#bfdbfe',
fontSize: 13,
fontWeight: '800',
textTransform: 'uppercase',
marginBottom: 6,
},
title: {
color: '#ffffff',
fontSize: 30,
fontWeight: '900',
},
});
El kicker es pequeño y azul claro. El title es grande y blanco.
Paso 4.3: Estilo del subtítulo
const styles = StyleSheet.create({
...
header: { ... },
kicker: { ... },
title: { ... },
subtitle: {
color: '#e0f2fe',
fontSize: 15,
marginTop: 8,
},
});
Después de seguir los pasos anteriores, tu archivo debería verse así:
import { ScrollView, View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function HomeScreen() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.header}>
<Text style={styles.kicker}>Sistema de inventario</Text>
<Text style={styles.title}>Inventario App</Text>
<Text style={styles.subtitle}>
Administra categorías y productos desde una aplicación móvil.
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
content: {
padding: 20,
},
header: {
backgroundColor: '#1a5276',
padding: 22,
borderRadius: 22,
marginBottom: 20,
},
kicker: {
color: '#bfdbfe',
fontSize: 13,
fontWeight: '800',
textTransform: 'uppercase',
marginBottom: 6,
},
title: {
color: '#ffffff',
fontSize: 30,
fontWeight: '900',
},
subtitle: {
color: '#e0f2fe',
fontSize: 15,
marginTop: 8,
},
});
En este punto, la pantalla debería tener un encabezado atractivo con fondo azul. El encabezado mostrará el nombre de la app y una descripción debajo.
Administra categorías y productos desde una aplicación móvil.
SafeAreaView para evitar problemas con el dispositivo.ScrollView para poder desplazarse si hay más contenido.Aunque más adelante los datos vendrán desde una API, por ahora usaremos datos temporales dentro de la aplicación.
Para eso utilizaremos el Hook useState, que nos permite guardar
datos dentro del componente.
En la parte superior del archivo, necesitamos importar useState desde React.
Esto nos permitirá usar estados en nuestro componente.
import { useState } from 'react';
import { ScrollView, View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
Dentro de la función HomeScreen, antes del return,
crearemos un arreglo de categorías usando useState.
Cada categoría tendrá dos propiedades:
id: Un número único para identificarla.nombre: El nombre de la categoría (ej: "Tecnología").const [categorias] = useState([
{ id: 1, nombre: 'Tecnología' },
{ id: 2, nombre: 'Accesorios' },
{ id: 3, nombre: 'Oficina' },
]);
Debajo del estado anterior, agregaremos un arreglo de productos. Cada producto estará asociado a una categoría.
Cada producto tiene:
id: Identificador único.nombre: Nombre del producto.precio: Precio del producto.stock: Cantidad disponible.categoriaId: Id de la categoría a la que pertenece.const [productos] = useState([
{
id: 1,
nombre: 'Laptop Dell',
precio: 850,
stock: 8,
categoriaId: 1,
},
{
id: 2,
nombre: 'Mouse inalámbrico',
precio: 18.5,
stock: 25,
categoriaId: 2,
},
{
id: 3,
nombre: 'Silla ejecutiva',
precio: 95,
stock: 4,
categoriaId: 3,
},
]);
Observa que la primera laptop tiene categoriaId: 1, lo que significa
que pertenece a "Tecnología". El mouse tiene categoriaId: 2 (Accesorios),
y la silla tiene categoriaId: 3 (Oficina).
Ahora crearemos algunos valores que nos servirán para mostrar estadísticas en la pantalla. Estos valores se calcularán automáticamente a partir de los datos.
const totalCategorias = categorias.length;
const totalProductos = productos.length;
const productosBajoStock = productos.filter(producto => producto.stock <= 5).length;
Estos valores funcionan así:
totalCategorias: Cuenta cuántas categorías hay (en este caso, 3).totalProductos: Cuenta cuántos productos hay (en este caso, 3).productosBajoStock: Cuenta cuántos productos tienen stock menor o igual a 5.
En nuestro ejemplo: la laptop (8) y el mouse (25) tienen stock normal, pero la silla (4)
tiene bajo stock. Resultado: 1 producto.En React Native es recomendable dividir la interfaz en componentes pequeños. Esto hace que el código sea más ordenado y fácil de mantener.
Este componente mostrará una estadística en una tarjeta. Por ejemplo, usaremos para mostrar: cantidad de categorías, cantidad de productos, y cantidad de productos con bajo stock.
Crea el archivo:
Este componente recibe tres datos (props) desde su padre:
titulo: El nombre de la estadística (ej: "Categorías").valor: El número a mostrar (ej: 3).descripcion: Texto adicional (ej: "Registradas").import { View, Text, StyleSheet } from 'react-native';
export default function StatCard({ titulo, valor, descripcion }) {
return (
<View style={styles.card}>
<Text style={styles.valor}>{valor}</Text>
<Text style={styles.titulo}>{titulo}</Text>
<Text style={styles.descripcion}>{descripcion}</Text>
</View>
);
}
La estructura es simple:
const styles = StyleSheet.create({
card: {
flex: 1,
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 18,
borderWidth: 1,
borderColor: '#e2e8f0',
},
valor: {
fontSize: 26,
fontWeight: '900',
color: '#1a5276',
},
titulo: {
fontSize: 14,
fontWeight: '800',
color: '#0f172a',
marginTop: 4,
},
descripcion: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
},
});
El estilo flex: 1 hace que la tarjeta crezca y ocupe el espacio
disponible junto a otras tarjetas.
Las props son datos que un componente recibe desde otro componente padre.
En este caso, cuando usemos StatCard, le pasaremos titulo,
valor y descripcion.
Primero, importa el componente en HomeScreen.js.
import StatCard from '../components/StatCard';
Luego, debajo del encabezado, agregaremos las tarjetas de estadísticas paso a paso.
Paso 1: Crear el contenedor para dos tarjetas
<View style={styles.statsContainer}>
<StatCard
titulo="Categorías"
valor={totalCategorias}
descripcion="Registradas"
/>
<StatCard
titulo="Productos"
valor={totalProductos}
descripcion="En inventario"
/>
</View>
Aquí mostramos dos tarjetas lado a lado: una para categorías y otra para productos. Cada una recibe props con el título, el valor numérico y una descripción.
Paso 2: Agregar una segunda fila con la tercera estadística
<View style={styles.statsContainer}>
<StatCard
titulo="Bajo stock"
valor={productosBajoStock}
descripcion="Requieren revisión"
/>
</View>
Esta segunda fila muestra una sola tarjeta: los productos con bajo stock. Es importante destacar esta métrica porque señala productos que necesitan atención.
Finalmente, agrega este estilo:
statsContainer: {
flexDirection: 'row',
gap: 12,
marginBottom: 12,
},
Ahora la pantalla debería mostrar el encabezado seguido de las tarjetas de estadísticas.
Administra categorías y productos desde una aplicación móvil.
El estilo flexDirection: 'row' hace que las tarjetas se muestren
una al lado de la otra. Con dos tarjetas caben bien. La tercera tarjeta va en una fila
aparte automáticamente.
Este es un componente pequeño y visual que mostrará el nombre de una categoría en una pequeña "píldora" redondeada. Es el chip naranja que ves en las tarjetas de productos.
Crea el archivo:
Es muy sencillo: recibe el nombre de la categoría y lo muestra con estilos especiales.
import { Text, StyleSheet } from 'react-native';
export default function CategoryPill({ nombre }) {
return (
<Text style={styles.pill}>
{nombre}
</Text>
);
}
Los estilos crean una "píldora" redondeada con fondo naranja:
const styles = StyleSheet.create({
pill: {
alignSelf: 'flex-start',
backgroundColor: '#ffedd5',
color: '#c2410c',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 999,
fontSize: 12,
fontWeight: '900',
marginTop: 8,
},
});
El borderRadius: 999 hace que sea completamente redondeado
(como una píldora). El alignSelf: 'flex-start' hace que ocupe solo
el ancho que necesita.
Este componente mostrará la información de cada producto en una tarjeta visual. Cada tarjeta mostrará el nombre, precio, stock, categoría y un badge indicando si el stock es bajo.
Crea el archivo:
Necesitamos importar los componentes de React Native y el componente CategoryPill
que creamos antes:
import { View, Text, StyleSheet } from 'react-native';
import CategoryPill from './CategoryPill';
Vamos a construir el componente gradualmente, agregando una parte a la vez.
Paso 2.1: Crear la tarjeta principal
Primero, creamos solo el contenedor exterior (la tarjeta blanca):
export default function ProductCard({ producto, categoria }) {
return (
<View style={styles.card}>
<Text>Producto</Text>
</View>
);
}
En este punto, solo mostramos una tarjeta blanca con un texto simple.
Paso 2.2: Agregar la estructura interna (row)
Ahora agregamos una fila (View con flexDirection: 'row') que divide el contenido en dos partes: la información a la izquierda y el badge a la derecha.
export default function ProductCard({ producto, categoria }) {
return (
<View style={styles.card}>
<View style={styles.row}>
<Text>Información del producto</Text>
<Text>Badge</Text>
</View>
</View>
);
}
Ahora la tarjeta tiene dos columnas: una para la información y otra para el badge.
Paso 2.3: Llenar la sección de información
Reemplazamos el texto de "Información" por un View que contiene todos los detalles:
export default function ProductCard({ producto, categoria }) {
return (
<View style={styles.card}>
<View style={styles.row}>
<View style={styles.info}>
<Text style={styles.nombre}>{producto.nombre}</Text>
<Text style={styles.detalle}>
Precio: ${producto.precio.toFixed(2)}
</Text>
<Text style={styles.detalle}>
Stock: {producto.stock} unidades
</Text>
<CategoryPill nombre={categoria} />
</View>
<Text>Badge</Text>
</View>
</View>
);
}
Ahora mostramos:
.toFixed(2)).Paso 2.4: Agregar el badge de estado
Finalmente, reemplazamos el "Badge" temporal por un View que muestra si el stock es bajo o normal:
export default function ProductCard({ producto, categoria }) {
return (
<View style={styles.card}>
<View style={styles.row}>
<View style={styles.info}>
<Text style={styles.nombre}>{producto.nombre}</Text>
<Text style={styles.detalle}>
Precio: ${producto.precio.toFixed(2)}
</Text>
<Text style={styles.detalle}>
Stock: {producto.stock} unidades
</Text>
<CategoryPill nombre={categoria} />
</View>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{producto.stock <= 5 ? 'Bajo' : 'OK'}
</Text>
</View>
</View>
</View>
);
}
El badge usa una condición: producto.stock <= 5 ? 'Bajo' : 'OK'.
Esto significa: "si el stock es menor o igual a 5, muestra 'Bajo', sino muestra 'OK'".
Construir los componentes gradualmente ayuda a entender cada parte. Si vemos todo de una vez, es más difícil de seguir. Así vamos de lo simple a lo complejo.
Agregaremos los estilos en el mismo orden que agregamos el código.
Paso 3.1: Estilos para la tarjeta principal
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 18,
marginBottom: 12,
borderWidth: 1,
borderColor: '#e2e8f0',
},
});
Esto crea una tarjeta blanca con bordes suaves y un pequeño espaciado debajo.
Paso 3.2: Agregar estilos para la fila
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 18,
marginBottom: 12,
borderWidth: 1,
borderColor: '#e2e8f0',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 12,
},
});
El flexDirection: 'row' coloca los elementos uno al lado del otro.
El justifyContent: 'space-between' los separa (uno a la izquierda, otro a la derecha).
Paso 3.3: Estilos para la información
const styles = StyleSheet.create({
card: { ... },
row: { ... },
info: {
flex: 1,
},
nombre: {
fontSize: 17,
fontWeight: '900',
color: '#0f172a',
},
detalle: {
color: '#64748b',
marginTop: 3,
},
});
El flex: 1 en info hace que ocupe todo el espacio disponible.
El nombre es texto grande y oscuro.
El detalle es más pequeño y gris.
Paso 3.4: Estilos finales para el badge
const styles = StyleSheet.create({
card: { ... },
row: { ... },
info: { ... },
nombre: { ... },
detalle: { ... },
badge: {
backgroundColor: '#e0f2fe',
borderRadius: 12,
paddingHorizontal: 10,
paddingVertical: 6,
alignSelf: 'flex-start',
},
badgeText: {
color: '#0369a1',
fontWeight: '900',
fontSize: 12,
},
});
El badge es una pequeña caja azul clara con esquinas redondeadas.
El alignSelf: 'flex-start' hace que ocupe solo el ancho que necesita.
Ahora usaremos este componente en la pantalla principal. Primero, importa el componente:
import ProductCard from '../components/ProductCard';
Los productos tienen categoriaId (un número), pero nosotros queremos mostrar
el nombre de la categoría (un texto). Por eso necesitamos una función que busque el nombre
a partir del ID.
Por ejemplo: si un producto tiene categoriaId: 1, esta función
debe retornar "Tecnología".
function obtenerNombreCategoria(categoriaId) {
const categoriaEncontrada = categorias.find(
categoria => categoria.id === categoriaId
);
return categoriaEncontrada ? categoriaEncontrada.nombre : 'Sin categoría';
}
Esta función:
Debajo de las tarjetas de estadísticas, agregamos un título para la sección de productos:
<View style={styles.sectionBlock}>
<Text style={styles.sectionTitle}>Productos recientes</Text>
</View>
Dentro de esa sección, agregamos un bloque que recorre todos los productos
y crea un ProductCard para cada uno.
Reemplaza el código anterior por este:
<View style={styles.sectionBlock}>
<Text style={styles.sectionTitle}>Productos recientes</Text>
{productos.map(producto => (
<ProductCard
key={producto.id}
producto={producto}
categoria={obtenerNombreCategoria(producto.categoriaId)}
/>
))}
</View>
El método .map() recorre cada producto del arreglo y ejecuta
una función para cada uno. En este caso:
producto, creamos un ProductCard.producto completo.key es importante para que React identifique cada elemento.sectionBlock: {
marginTop: 10,
},
sectionTitle: {
fontSize: 20,
fontWeight: '900',
color: '#0f172a',
marginBottom: 12,
},
Ahora la pantalla debería mostrar encabezado, estadísticas y una lista de productos.
Administra categorías y productos desde una aplicación móvil.
Productos recientes
$850.00 - Stock: 8
Tecnología$18.50 - Stock: 25
Accesorios$95.00 - Stock: 4
OficinaAl terminar todos los pasos anteriores, los archivos principales deben quedar así.
import { StatusBar } from 'expo-status-bar';
import HomeScreen from './src/screens/HomeScreen';
export default function App() {
return (
<>
<HomeScreen />
<StatusBar style="light" />
</>
);
}
import { useState } from 'react';
import { ScrollView, View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import StatCard from '../components/StatCard';
import ProductCard from '../components/ProductCard';
export default function HomeScreen() {
const [categorias] = useState([
{ id: 1, nombre: 'Tecnología' },
{ id: 2, nombre: 'Accesorios' },
{ id: 3, nombre: 'Oficina' },
]);
const [productos] = useState([
{
id: 1,
nombre: 'Laptop Dell',
precio: 850,
stock: 8,
categoriaId: 1,
},
{
id: 2,
nombre: 'Mouse inalámbrico',
precio: 18.5,
stock: 25,
categoriaId: 2,
},
{
id: 3,
nombre: 'Silla ejecutiva',
precio: 95,
stock: 4,
categoriaId: 3,
},
]);
const totalCategorias = categorias.length;
const totalProductos = productos.length;
const productosBajoStock = productos.filter(producto => producto.stock <= 5).length;
function obtenerNombreCategoria(categoriaId) {
const categoriaEncontrada = categorias.find(
categoria => categoria.id === categoriaId
);
return categoriaEncontrada ? categoriaEncontrada.nombre : 'Sin categoría';
}
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.header}>
<Text style={styles.kicker}>Sistema de inventario</Text>
<Text style={styles.title}>Inventario App</Text>
<Text style={styles.subtitle}>
Administra categorías y productos desde una aplicación móvil.
</Text>
</View>
<View style={styles.statsContainer}>
<StatCard
titulo="Categorías"
valor={totalCategorias}
descripcion="Registradas"
/>
<StatCard
titulo="Productos"
valor={totalProductos}
descripcion="En inventario"
/>
</View>
<View style={styles.statsContainer}>
<StatCard
titulo="Bajo stock"
valor={productosBajoStock}
descripcion="Requieren revisión"
/>
</View>
<View style={styles.sectionBlock}>
<Text style={styles.sectionTitle}>Productos recientes</Text>
{productos.map(producto => (
<ProductCard
key={producto.id}
producto={producto}
categoria={obtenerNombreCategoria(producto.categoriaId)}
/>
))}
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
content: {
padding: 20,
paddingBottom: 40,
},
header: {
backgroundColor: '#1a5276',
padding: 22,
borderRadius: 22,
marginBottom: 20,
},
kicker: {
color: '#bfdbfe',
fontSize: 13,
fontWeight: '800',
textTransform: 'uppercase',
marginBottom: 6,
},
title: {
color: '#ffffff',
fontSize: 30,
fontWeight: '900',
},
subtitle: {
color: '#e0f2fe',
fontSize: 15,
marginTop: 8,
},
statsContainer: {
flexDirection: 'row',
gap: 12,
marginBottom: 12,
},
sectionBlock: {
marginTop: 10,
},
sectionTitle: {
fontSize: 20,
fontWeight: '900',
color: '#0f172a',
marginBottom: 12,
},
});
import { View, Text, StyleSheet } from 'react-native';
export default function StatCard({ titulo, valor, descripcion }) {
return (
<View style={styles.card}>
<Text style={styles.valor}>{valor}</Text>
<Text style={styles.titulo}>{titulo}</Text>
<Text style={styles.descripcion}>{descripcion}</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
flex: 1,
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 18,
borderWidth: 1,
borderColor: '#e2e8f0',
},
valor: {
fontSize: 26,
fontWeight: '900',
color: '#1a5276',
},
titulo: {
fontSize: 14,
fontWeight: '800',
color: '#0f172a',
marginTop: 4,
},
descripcion: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
},
});
import { Text, StyleSheet } from 'react-native';
export default function CategoryPill({ nombre }) {
return (
<Text style={styles.pill}>
{nombre}
</Text>
);
}
const styles = StyleSheet.create({
pill: {
alignSelf: 'flex-start',
backgroundColor: '#ffedd5',
color: '#c2410c',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 999,
fontSize: 12,
fontWeight: '900',
marginTop: 8,
},
});
import { View, Text, StyleSheet } from 'react-native';
import CategoryPill from './CategoryPill';
export default function ProductCard({ producto, categoria }) {
return (
<View style={styles.card}>
<View style={styles.row}>
<View style={styles.info}>
<Text style={styles.nombre}>{producto.nombre}</Text>
<Text style={styles.detalle}>
Precio: ${producto.precio.toFixed(2)}
</Text>
<Text style={styles.detalle}>
Stock: {producto.stock} unidades
</Text>
<CategoryPill nombre={categoria} />
</View>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{producto.stock <= 5 ? 'Bajo' : 'OK'}
</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 18,
marginBottom: 12,
borderWidth: 1,
borderColor: '#e2e8f0',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 12,
},
info: {
flex: 1,
},
nombre: {
fontSize: 17,
fontWeight: '900',
color: '#0f172a',
},
detalle: {
color: '#64748b',
marginTop: 3,
},
badge: {
backgroundColor: '#e0f2fe',
borderRadius: 12,
paddingHorizontal: 10,
paddingVertical: 6,
alignSelf: 'flex-start',
},
badgeText: {
color: '#0369a1',
fontWeight: '900',
fontSize: 12,
},
});
Ahora es tu turno. Realiza los siguientes cambios en la aplicación.
Cambia el título Inventario App por el nombre de tu propia aplicación.
Agrega dos categorías nuevas al arreglo de categorías.
Agrega tres productos nuevos al arreglo de productos. Recuerda que cada producto debe tener:
idnombrepreciostockcategoriaIdModifica los colores del encabezado y de las tarjetas para personalizar la apariencia de la app.