Backend API para la aplicación móvil Sparpal de comparación de precios de supermercados alemanes.
- Node.js + Express.js
- PostgreSQL como base de datos
- Sequelize ORM para manejo de base de datos
- JWT (jsonwebtoken) para autenticación
- bcryptjs para hashing de contraseñas
- dotenv para variables de entorno
- ✅ Autenticación JWT con refresh tokens
- ✅ CRUD completo para productos, precios y supermercados
- ✅ Gestión de listas de compras
- ✅ Sistema de suscripciones (free/premium)
- ✅ CORS configurado para React Native (Expo)
- ✅ Rate limiting y seguridad con Helmet
- ✅ Manejo global de errores
- ✅ Logging con Morgan
- ✅ Migraciones y seeders con Sequelize CLI
- ✅ Sistema de scraping para 5 supermercados alemanes
- ✅ Job programado diario (06:00 AM UTC)
- ✅ Fallback automático a datos mock
- ✅ Panel de administración completo
git clone <repository-url>
cd sparpal-backendnpm installcp .env.example .envEdita el archivo .env con tus configuraciones:
# Database Configuration
DATABASE_URL=postgresql://username:password@localhost:5432/sparpal_db
DB_HOST=localhost
DB_PORT=5432
DB_NAME=sparpal_db
DB_USER=tu_usuario
DB_PASSWORD=tu_password
# JWT Configuration
JWT_SECRET=tu-super-secreto-jwt-key-cambiar-en-produccion
JWT_REFRESH_SECRET=tu-super-secreto-refresh-jwt-key-cambiar-en-produccion
JWT_EXPIRES_IN=24h
JWT_REFRESH_EXPIRES_IN=7d
# Server Configuration
NODE_ENV=development
PORT=3000
# CORS Configuration
CORS_ORIGIN=http://localhost:19000
# Logging
LOG_LEVEL=info- Instalar PostgreSQL (si no está instalado)
- Crear la base de datos:
createdb sparpal_db
# Ejecutar migraciones (crear tablas)
npm run migrate
# Poblar con datos iniciales (opcional)
npm run seed# Modo desarrollo
npm run dev
# Modo producción
npm startEl servidor estará ejecutándose en http://localhost:3000
Registrar nuevo usuario.
Request Body:
{
"email": "user@example.com",
"password": "SecurePassword123",
"name": "Juan Pérez"
}Response:
{
"message": "User created successfully",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "Juan Pérez",
"created_at": "2024-01-01T00:00:00.000Z"
},
"token": "jwt-token",
"refreshToken": "refresh-token"
}Iniciar sesión.
Request Body:
{
"email": "user@example.com",
"password": "SecurePassword123"
}Renovar token de acceso.
Request Body:
{
"refreshToken": "refresh-token"
}Obtener perfil del usuario (requiere autenticación).
Obtener todos los supermercados.
Obtener supermercado por ID.
Obtener productos con búsqueda opcional.
Query Parameters:
name- Buscar por nombrecategory- Filtrar por categoríalimit- Límite de resultados (default: 50)offset- Offset para paginación
Obtener producto por ID con precios.
Obtener precios con filtros opcionales.
Query Parameters:
productId- Filtrar por productosupermarketId- Filtrar por supermercadolimit- Límite de resultadosoffset- Offset para paginación
Obtener todos los precios de un producto específico.
Obtener suscripción actual del usuario.
Crear o actualizar suscripción.
Request Body:
{
"plan": "premium",
"expires_at": "2024-12-31T23:59:59.000Z"
}Obtener todas las listas de compras del usuario.
Crear nueva lista de compras.
Request Body:
{
"name": "Lista semanal"
}Obtener lista específica.
Actualizar lista.
Eliminar lista.
Agregar producto a lista.
Request Body:
{
"product_id": "product-uuid",
"quantity": 2,
"notes": "Verde, sin daños"
}Actualizar item de lista.
Eliminar item de lista.
Health check del servidor.
Response:
{
"status": "OK",
"timestamp": "2024-01-01T00:00:00.000Z",
"uptime": 3600,
"environment": "development"
}# Ejecutar tests
npm testsparpal-backend/
├── src/
│ ├── config/
│ │ ├── database.js # Configuración de base de datos
│ │ └── env.js # Variables de entorno
│ ├── controllers/ # Lógica de negocio
│ │ ├── authController.js
│ │ ├── productController.js
│ │ ├── priceController.js
│ │ ├── supermarketController.js
│ │ ├── subscriptionController.js
│ │ └── shoppingListController.js
│ ├── middleware/
│ │ └── auth.js # Middleware de autenticación JWT
│ ├── models/ # Modelos de Sequelize
│ │ ├── User.js
│ │ ├── Subscription.js
│ │ ├── Supermarket.js
│ │ ├── Product.js
│ │ ├── Price.js
│ │ ├── ShoppingList.js
│ │ ├── ShoppingListItem.js
│ │ └── index.js # Configuración de modelos y relaciones
│ ├── routes/ # Rutas de Express
│ │ ├── auth.js
│ │ ├── products.js
│ │ ├── prices.js
│ │ ├── supermarkets.js
│ │ ├── subscriptions.js
│ │ └── shoppingLists.js
│ ├── app.js # Configuración principal de Express
│ └── server.js # Entry point del servidor
├── migrations/ # Migraciones de base de datos
├── seeders/ # Datos iniciales
├── .env.example # Ejemplo de variables de entorno
├── .gitignore
├── package.json
└── README.md
- Rate Limiting: 100 requests por 15 minutos por IP
- Helmet: Headers de seguridad HTTP
- CORS: Configurado específicamente para React Native (Expo)
- bcryptjs: Hash de contraseñas con salt rounds
- JWT: Tokens con expiración configurada
- Validación: Validación de entrada con express-validator
npm start- Iniciar servidor en producciónnpm run dev- Iniciar servidor en desarrollo con nodemonnpm test- Ejecutar testsnpm run migrate- Ejecutar migraciones de base de datosnpm run migrate:undo- Revertir última migraciónnpm run seed- Ejecutar seedersnpm run seed:undo- Revertir seeders
id(UUID, PK)email(STRING, UNIQUE)password_hash(STRING)name(STRING)created_at,updated_at(TIMESTAMP)
id(UUID, PK)name(STRING, UNIQUE) - REWE, EDEKA, ALDI, LIDL, PENNYcity(STRING) - Berlin (MVP)website_url(STRING)created_at,updated_at(TIMESTAMP)
id(UUID, PK)name(STRING)category(STRING)unit(ENUM: 'kg', 'L', 'unit', 'piece')description(TEXT)created_at,updated_at(TIMESTAMP)
id(UUID, PK)product_id(UUID, FK)supermarket_id(UUID, FK)price(DECIMAL(10,2))price_per_unit(DECIMAL(10,2))last_updated(TIMESTAMP)source(ENUM: 'scraper', 'manual')created_at,updated_at(TIMESTAMP)
id(UUID, PK)user_id(UUID, FK)plan(ENUM: 'free', 'premium')started_at(TIMESTAMP)expires_at(TIMESTAMP, NULL allowed)created_at,updated_at(TIMESTAMP)
id(UUID, PK)user_id(UUID, FK)name(STRING)created_at,updated_at(TIMESTAMP)
id(UUID, PK)shopping_list_id(UUID, FK)product_id(UUID, FK)quantity(INTEGER)notes(TEXT)created_at,updated_at(TIMESTAMP)
| Variable | Descripción | Default |
|---|---|---|
DATABASE_URL |
URL de conexión completa a PostgreSQL | - |
DB_HOST |
Host de PostgreSQL | localhost |
DB_PORT |
Puerto de PostgreSQL | 5432 |
DB_NAME |
Nombre de la base de datos | sparpal_db |
DB_USER |
Usuario de PostgreSQL | - |
DB_PASSWORD |
Password de PostgreSQL | - |
JWT_SECRET |
Secret para JWT tokens | - |
JWT_REFRESH_SECRET |
Secret para refresh tokens | - |
JWT_EXPIRES_IN |
Expiración de tokens | 24h |
JWT_REFRESH_EXPIRES_IN |
Expiración de refresh tokens | 7d |
NODE_ENV |
Entorno de ejecución | development |
PORT |
Puerto del servidor | 3000 |
CORS_ORIGIN |
Origen permitido para CORS | http://localhost:19000 |
LOG_LEVEL |
Nivel de logging | info |
npx sequelize-cli migration:generate --name nombre-de-la-migracionnpx sequelize-cli seed:generate --name nombre-del-seedernpx sequelize-cli db:migrate --env development- Sistema de scraping implementado para REWE, EDEKA, ALDI, LIDL y PENNY
- Datos mock realistas con precios alemanes actuales
- Job diario automático a las 06:00 AM UTC
- Fallback automático a datos mock si falla el scraper
- CORS configurado para React Native (Expo) en
http://localhost:19000 - Sistema de logging básico implementado con Morgan
- Error handling global implementado
El sistema incluye scrapers para 5 supermercados alemanes con:
- REWE
- EDEKA
- ALDI
- LIDL
- PENNY
- Leche 1L, Pan, Huevos (6), Manzanas 1kg
- Aceite, Sal, Azúcar 1kg, Pasta, Tomates (lata)
- ✅ 3 reintentos con 5s de delay
- ✅ 3s de delay entre supermercados
- ✅ Fallback automático a mock data
- ✅ Logs completos en tabla
scrape_logs - ✅ Trigger manual desde panel admin
Ver documentación completa en SCRAPING_README.md
- Reemplazar datos mock con scraping web real
- Agregar tests unitarios e integración
- Documentación OpenAPI/Swagger
- Docker para deployment
- Sistema de notificaciones push
- Analytics y métricas
- Cache con Redis para mejorar performance