Para portainer

This commit is contained in:
2026-06-23 12:29:53 -05:00
parent ec093544a8
commit c9a28cbb39
7 changed files with 268 additions and 11 deletions
+5 -1
View File
@@ -9,9 +9,13 @@ RUN npm install --production
# Copiar el código del backend
COPY backend/ ./
# Copiar el frontend (servido como estático)
# Copiar el frontend empaquetado (no se monta como volumen en producción)
COPY frontend/ ./frontend/
# Script de entrada: crea datos/carpetas por defecto si no existen en el volumen
RUN chmod +x ./docker-entrypoint.sh
EXPOSE 3000
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["node", "server.js"]
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
set -e
# Crear carpetas de datos si no existen (volúmenes vacíos en PC nuevo)
mkdir -p /app/data
mkdir -p /app/Imagenes
mkdir -p /app/Imagenes/agotados
mkdir -p /app/Imagenes/sin-categoria
mkdir -p /app/pedidos
mkdir -p /app/videos
# Crear contraseñas.json por defecto si no existe, con admin/admin
if [ ! -f /app/data/contraseñas.json ]; then
echo '{"usuarios":[{"usuario":"admin","contraseña":"admin","rol":"admin","permisos":[]}]}' > /app/data/contraseñas.json
echo "[entrypoint] ✅ Creado admin por defecto: usuario=admin / contraseña=admin"
fi
# Crear el resto de JSON base si no existen
[ -f /app/data/productos.json ] || echo '[]' > /app/data/productos.json
[ -f /app/data/categorias.json ] || echo '[]' > /app/data/categorias.json
[ -f /app/data/sesiones.json ] || echo '[]' > /app/data/sesiones.json
[ -f /app/data/vendedores.json ] || echo '[]' > /app/data/vendedores.json
[ -f /app/data/redes.json ] || echo '[]' > /app/data/redes.json
[ -f /app/data/pedidos-meta.json ] || echo '[]' > /app/data/pedidos-meta.json
# Asegurar que el backend pueda escribir en las carpetas montadas
chown -R node:node /app/data /app/Imagenes /app/pedidos /app/videos 2>/dev/null || true
exec "$@"
+58 -5
View File
@@ -25,16 +25,61 @@ const IMG_DIR = path.join(__dirname, 'Imagenes');
const PEDIDOS_DIR = path.join(__dirname, 'pedidos');
const VIDEOS_DIR = path.join(__dirname, 'videos');
// ════════════════════════════════
// INICIALIZACIÓN DE DATOS / CARPETAS
// Se ejecuta antes de arrancar el servidor. Útil cuando se montan volúmenes
// vacíos en un PC nuevo por primera vez.
// ════════════════════════════════
async function initDataFiles() {
try {
await fs.mkdir(DATA_DIR, { recursive: true });
await fs.mkdir(IMG_DIR, { recursive: true });
await fs.mkdir(path.join(IMG_DIR, 'agotados'), { recursive: true });
await fs.mkdir(path.join(IMG_DIR, SIN_CATEGORIA_ID), { recursive: true });
await fs.mkdir(PEDIDOS_DIR, { recursive: true });
await fs.mkdir(VIDEOS_DIR, { recursive: true });
const defaults = {
'contraseñas.json': { usuarios: [{ usuario: 'admin', contraseña: 'admin', rol: 'admin', permisos: [] }] },
'productos.json': [],
'categorias.json': [],
'sesiones.json': [],
'vendedores.json': [],
'redes.json': [],
'pedidos-meta.json': []
};
for (const [file, value] of Object.entries(defaults)) {
const filePath = path.join(DATA_DIR, file);
try {
await fs.access(filePath);
} catch {
await fs.writeFile(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
console.log(`[init] Creado ${file} por defecto`);
}
}
} catch (err) {
console.error('[init] Error inicializando datos:', err.message);
}
}
// ════════════════════════════════
// MIDDLEWARES
// ════════════════════════════════
app.use(express.json());
app.use(cookieParser(SECRET));
// Servir frontend estático con index.html como fallback de carpeta
app.use(express.static(FRONTEND_DIR));
app.use('/Imagenes', express.static(IMG_DIR, { maxAge: '7d' }));
app.use('/pedidos', express.static(PEDIDOS_DIR));
app.use('/videos', express.static(VIDEOS_DIR));
// Ruta raíz explícita: sirve el catálogo principal
app.get('/', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'index.html'));
});
// Evita el 404 de favicon.ico
app.get('/favicon.ico', (req, res) => res.status(204).end());
@@ -1506,7 +1551,9 @@ app.use((err, req, res, next) => {
});
// ════════════════════════════════
// FALLBACK para SPA-like del admin
// FALLBACK para SPA-like del admin y catálogo
// Sirve index.html para rutas desconocidas que parezcan de la SPA.
// ⚠️ Debe ir DESPUÉS de todas las rutas de API.
// ════════════════════════════════
app.get('/administrativo', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'administrativo', 'index.html'));
@@ -1514,6 +1561,9 @@ app.get('/administrativo', (req, res) => {
app.get('/administrativo/', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'administrativo', 'index.html'));
});
app.get('/administrativo/*', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'administrativo', 'index.html'));
});
// ════════════════════════════════
// LIMPIEZA DE SESIONES A LAS 00:00 (America/Bogota)
@@ -2389,7 +2439,10 @@ app.get('/api/redes/proxy', async (req, res) => {
// ════════════════════════════════
// START
// ════════════════════════════════
app.listen(PORT, () => {
console.log(`∞ Infinity server corriendo en http://localhost:${PORT}`);
scheduleSessionCleanup();
});
(async () => {
await initDataFiles();
app.listen(PORT, () => {
console.log(`∞ Infinity server corriendo en http://localhost:${PORT}`);
scheduleSessionCleanup();
});
})();