Aplicación web para administrar un catálogo maestro de SKU, relaciones por holding,
staging, importaciones y sincronización opcional con Firebase. Node.js/Express sirve
la SPA y mantiene los archivos JSON de local_data/ como fuente oficial de verdad.
Los Excel se procesan y descartan: nunca se almacenan en el backend.
- Node.js 22 LTS (Node 20 o superior también es compatible).
- npm.
- Un directorio persistente con permisos de escritura para los JSON.
cp .env.example .env
npm ci
npm test
npm startLa aplicación queda disponible en http://localhost:3000 y el healthcheck en
http://localhost:3000/health.
src/
config/ configuración y valores iniciales
controllers/ adaptación HTTP
docs/ contrato OpenAPI
errors/ errores de aplicación
middleware/ seguridad, validación, logging y errores
routes/ definición de endpoints
services/ negocio, Firebase, auditoría y APIs externas
storage/ única capa con conocimiento de archivos físicos
utils/ utilidades puras
src/server.js gestiona el ciclo de vida; src/app.js construye Express sin abrir
puertos; y StorageService concentra lectura, caché, escritura atómica y rutas. Los
catálogos existentes no cambian de nombre ni de formato.
Use .env.example como referencia. Las opciones principales son:
| Variable | Predeterminado | Uso |
|---|---|---|
PORT / HOST |
3000 / 0.0.0.0 |
Escucha HTTP |
DATA_DIR |
local_data |
Única raíz de persistencia |
LOG_LEVEL |
debug desarrollo, info producción |
Nivel Pino |
CORS_ORIGINS |
* |
Orígenes separados por coma |
TRUST_PROXY |
1 |
Saltos de proxy confiables |
JSON_BODY_LIMIT |
50mb |
Límite de requests JSON |
RATE_LIMIT_MAX |
600 |
Requests por ventana/IP |
MAX_IMPORT_RECORDS |
100000 |
Registros máximos por lote |
STORAGE_CACHE_TTL_MS |
5000 |
TTL de caché JSON |
FIREBASE_ENABLED |
true |
Activa Firebase si existe credencial |
DISABLE_FIREBASE_SYNC |
0 |
Switch heredado; 1 desactiva |
FIREBASE_PROJECT_ID |
Credencial JSON | Proyecto esperado; detecta claves incorrectas |
FIREBASE_DATABASE_ID |
(default) |
Base Firestore que se debe consultar |
EXTERNAL_API_URL |
Open Food Facts | Servicio de enriquecimiento |
OPEN_PRODUCTS_API_URL |
Open Products Facts | Enriquecimiento alternativo |
SOLOTODO_API_URL |
API pública SoloTodo | Búsqueda exacta de EAN |
No guarde secretos en .env.example. La credencial Firebase debe existir, por
defecto, en DATA_DIR/firebase-key.json, con permisos solo para el usuario del
servicio. Si Firebase se desactiva o la credencial no existe, la aplicación continúa
sin errores ni intentos de sincronización.
Para comprobar la configuración del servidor antes del corte DNS:
curl -s http://127.0.0.1:3000/api/last-syncCuando Firebase está activo, la respuesta incluye projectId y databaseId. Un error
Firestore NOT_FOUND suele indicar que el proceso desplegado apunta a otro proyecto o
base. Confirme que FIREBASE_PROJECT_ID coincide con project_id dentro de la
credencial, que FIREBASE_DATABASE_ID identifica una base existente y reinicie el
contenedor/servicio después de modificar .env.
Las respuestas normales usan { "success": true, "data": ... }; los errores usan
{ "success": false, "error": "..." }. Nunca se expone un stack trace. El contrato
completo está en GET /api/openapi.json.
Endpoints principales:
GET|POST|DELETE /api/productsPOST /api/products/bulkGET|POST /api/holdingsDELETE /api/holdings/:idGET|POST /api/storesGET|POST /api/category-hierarchyGET|POST /api/staging/:keyPOST /api/sync-firebaseyGET /api/last-syncGET /api/import-history
El historial de importaciones almacena solo fecha, cantidad de registros, usuario
opcional, duración y resultado. Está limitado a las últimas
IMPORT_HISTORY_LIMIT entradas.
docker build -t sku-data-manager:latest .
docker run -d --name sku-data-manager \
--env-file .env \
-p 127.0.0.1:3000:3000 \
-v /srv/sku-data/local_data:/app/local_data \
--restart unless-stopped \
sku-data-manager:latestLa imagen es multi-stage, ejecuta como usuario no root, excluye datos y credenciales, y contiene healthcheck. Prepare el volumen:
sudo install -d -o 1000 -g 1000 -m 750 /srv/sku-data/local_dataInstale Node.js LTS, clone en /opt/sku-data-manager, ejecute npm ci --omit=dev,
copie .env.example a .env y configure un servicio:
[Unit]
Description=Smart Shelf SKU Data Manager
After=network-online.target
[Service]
Type=simple
User=sku-data
Group=sku-data
WorkingDirectory=/opt/sku-data-manager
EnvironmentFile=/opt/sku-data-manager/.env
ExecStart=/usr/bin/node /opt/sku-data-manager/server.js
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=15
[Install]
WantedBy=multi-user.targetDespués: sudo systemctl daemon-reload && sudo systemctl enable --now sku-data-manager.
No requiere cambios en la aplicación. Mantenga TRUST_PROXY=1 para un único Nginx:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
client_max_body_size 50m;
}Termine TLS en Nginx y limite el puerto 3000 a localhost/firewall.
Detenga escrituras o el servicio durante el snapshot y copie todo DATA_DIR; no
seleccione archivos individuales porque catálogo y relaciones forman una unidad:
sudo systemctl stop sku-data-manager
sudo tar -C /srv/sku-data -czf "/srv/backups/sku-data-$(date +%F-%H%M).tgz" local_data
sudo systemctl start sku-data-managerPara restaurar, detenga el servicio, conserve una copia del estado actual, extraiga
el backup sobre DATA_DIR, verifique propietario/permisos y arranque. Pruebe backups
periódicamente. Nunca incluya Excel en el volumen.
npm run test:install-browsers # una vez: instala Chromium para Playwright
npm test # sintaxis, ESLint, Jest/Supertest y Playwright
npm run test:jest # sólo pruebas unitarias, integración API y regresión
npm run test:e2e # sólo recorridos End-to-End en Chromium
npm run format # Prettier
npm run dev # reinicio automático localLas pruebas no usan local_data/: Jest crea directorios temporales y Playwright
levanta un backend aislado en el puerto 4173, desactiva Firebase y elimina sus
datos al terminar. Consulte TESTING.md para ver todos los scripts,
la organización de la suite, cobertura, ejecución headed y solución de problemas.
La aplicación registra JSON estructurado por stdout. En producción use el recolector
de Docker/journald y no archivos internos de log. SIGTERM y SIGINT detienen nuevas
conexiones, esperan escrituras pendientes y liberan timers.