Version CGL
This commit is contained in:
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
# Stage 1: Build
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
# En Docker, los comandos se ejecutan como root por defecto, no hace falta sudo
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Production
|
||||
FROM nginx:stable-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
90
control.sh
Normal file
90
control.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colores para los mensajes
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Función de ayuda
|
||||
show_help() {
|
||||
echo -e "${BLUE}===================== CONTROL DEL BLOG DE JOSEMI =================${NC}"
|
||||
echo " "
|
||||
echo " Uso: ./control.sh [comando] "
|
||||
echo ""
|
||||
echo "Comandos disponibles:"
|
||||
echo -e " ${GREEN}start${NC} -> Levanta todo el entorno (en segundo plano)"
|
||||
echo -e " ${GREEN}stop${NC} -> Detiene los contenedores (sin borrarlos)"
|
||||
echo -e " ${GREEN}restart${NC} -> Reinicia los contenedores"
|
||||
echo -e " ${GREEN}build${NC} -> Reconstruye las imágenes (útil si instalas nuevas librerías)"
|
||||
echo -e " ${GREEN}logs${NC} -> Ver logs de todo en tiempo real (Ctrl+C para salir)"
|
||||
echo -e " ${GREEN}status${NC} -> Ver estado de los contenedores"
|
||||
echo -e " ${GREEN}shell-be${NC} -> Entrar a la terminal del Backend"
|
||||
echo -e " ${GREEN}shell-fe${NC} -> Entrar a la terminal del Frontend"
|
||||
echo -e " ${RED}reset${NC} -> ¡PELIGRO! Borra la Base de Datos y la crea de cero (útil si cambias contraseñas)"
|
||||
echo -e " ${YELLOW}info${NC} -> Muestra las URLs y credenciales de acceso"
|
||||
echo -e " ${RED}down${NC} -> Deletea todo el container"
|
||||
echo "=================================================================="
|
||||
}
|
||||
|
||||
# Lógica del script
|
||||
case "$1" in
|
||||
start)
|
||||
echo -e "${GREEN}Arrancando el sistema...${NC}"
|
||||
sudo docker-compose up -d
|
||||
echo -e "${BLUE}¡Listo! Usa './control.sh info' para ver los accesos.${NC}"
|
||||
;;
|
||||
stop)
|
||||
echo -e "${YELLOW}Deteniendo contenedores...${NC}"
|
||||
sudo docker-compose stop
|
||||
;;
|
||||
restart)
|
||||
echo -e "${YELLOW}Reiniciando...${NC}"
|
||||
sudo docker-compose restart
|
||||
;;
|
||||
build)
|
||||
echo -e "${BLUE}Reconstruyendo imágenes (esto puede tardar)...${NC}"
|
||||
sudo docker-compose up -d --build
|
||||
;;
|
||||
logs)
|
||||
echo -e "${BLUE}Mostrando logs (Ctrl+C para salir)...${NC}"
|
||||
sudo docker-compose logs -f
|
||||
;;
|
||||
status)
|
||||
sudo docker-compose ps
|
||||
;;
|
||||
shell-be)
|
||||
echo -e "${GREEN}Entrando al contenedor del Backend...${NC}"
|
||||
sudo docker exec -it blog_backend sh
|
||||
;;
|
||||
shell-fe)
|
||||
echo -e "${GREEN}Entrando al contenedor del Frontend...${NC}"
|
||||
sudo docker exec -it blog_frontend sh
|
||||
;;
|
||||
reset)
|
||||
echo -e "${RED}ATENCIÓN: Esto borrará todos los datos de la base de datos.${NC}"
|
||||
read -p "¿Estás seguro? (s/n): " confirm
|
||||
if [[ $confirm == [sS] || $confirm == [sS][yY] ]]; then
|
||||
echo -e "${YELLOW}Borrando todo...${NC}"
|
||||
sudo docker-compose down -v
|
||||
sudo docker system prune -a
|
||||
echo -e "${GREEN}Levantando de nuevo...${NC}"
|
||||
sudo docker-compose up -d
|
||||
else
|
||||
echo "Operación cancelada."
|
||||
fi
|
||||
;;
|
||||
info)
|
||||
echo -e "${BLUE}=== INFORMACIÓN DE ACCESO ===${NC}"
|
||||
echo -e "WEB: ${GREEN}http://localhost:3000${NC}"
|
||||
echo -e "${BLUE}======================${NC}"
|
||||
;;
|
||||
down)
|
||||
sudo docker-compose down -v
|
||||
sudo docker system prune -a
|
||||
;;
|
||||
*)
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
7
docker-compose.yml
Normal file
7
docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
frontend:
|
||||
build: .
|
||||
container_name: creapeticiones_frontend
|
||||
ports:
|
||||
- "3000:80"
|
||||
restart: always
|
||||
12
index.html
Normal file
12
index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Creapeticiones | Generador</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
package.json
Normal file
19
package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "creapeticiones",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"vite": "^4.3.0"
|
||||
}
|
||||
}
|
||||
184
src/App.jsx
Normal file
184
src/App.jsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function App() {
|
||||
const [pagina, setPagina] = useState('menu');
|
||||
const [resultado, setResultado] = useState('-----');
|
||||
const [filtros, setFiltros] = useState({
|
||||
origen: '', destino: '', operador: 'Renfe Mercancías', ultHora: false, inversion: false
|
||||
});
|
||||
|
||||
// --- BASE DE DATOS ESTRUCTURADA ---
|
||||
const estacionesSur = [
|
||||
"Estación Sur", "Ingeniero Pizias", "Carlos Abadias", "Bifuracion Anillo", "Princesa Susana",
|
||||
"Aguja - Princesa Susana", "Santa Ana", "CAF", "Gadea", "Armengol", "Domingo Gil",
|
||||
"Pueblo Novo", "Aguja-Central Combustible", "Casacota", "Pinares", "Suso", "Los Zorros",
|
||||
"Carles Romero", "Dos hermanos", "Genzor", "Aranda", "Bifurcacion Aranda", "Huerta", "Calera", "Orobon"
|
||||
];
|
||||
|
||||
const estacionesNorte = [
|
||||
"Carthago", "Llamas", "Castañeda", "Arenillas", "Pujazon", "Sanchez - Avila",
|
||||
"Poveda", "Estacion Norte", "Rovira", "Aguja Salvador Hernandez", "Salvador y Hernandez - Plaza"
|
||||
];
|
||||
|
||||
// Eje para calcular paridad (Orden de NORTE a SUR = PAR)
|
||||
const ejePar = [
|
||||
"Salvador y Hernandez - Plaza", "Aguja Salvador Hernandez", "Rovira", "Estacion Norte",
|
||||
"Poveda", "Sanchez - Avila", "Pujazon", "Arenillas", "Castañeda", "Llamas", "Carthago",
|
||||
"Calera", "Huerta", "Bifurcacion Aranda", "Genzor", "Dos hermanos", "Aranda", "Carles Romero",
|
||||
"Los Zorros", "Orobon", "Princesa Susana", "Aguja - Princesa Susana", "Santa Ana", "CAF",
|
||||
"Ingeniero Pizias", "Carlos Abadias", "Estación Sur"
|
||||
];
|
||||
|
||||
const todasLasEstaciones = [...new Set([...estacionesSur, ...estacionesNorte])].sort();
|
||||
|
||||
// --- SISTEMA DE MEMORIA ---
|
||||
const getUltimo = (tipo) => parseInt(localStorage.getItem(`ultimo_${tipo}`)) || 1000;
|
||||
const saveUltimo = (tipo, val) => localStorage.setItem(`ultimo_${tipo}`, val);
|
||||
|
||||
const generarPeticion = (esEspecial) => {
|
||||
const { origen, destino, operador, ultHora, inversion } = filtros;
|
||||
if (!origen || !destino) return alert("Error: Debe indicar origen y destino.");
|
||||
|
||||
// 1. DETERMINAR SENTIDO Y PARIDAD
|
||||
const idxO = ejePar.indexOf(origen);
|
||||
const idxD = ejePar.indexOf(destino);
|
||||
|
||||
// Si baja (de Norte a Sur) es PAR. Si sube es IMPAR.
|
||||
// Ramales específicos: Santa Ana-Armengol y ramal Casacota se consideran subida (IMPAR)
|
||||
let paridadRequerida = "IMPAR";
|
||||
if (idxO < idxD && idxO !== -1 && idxD !== -1) paridadRequerida = "PAR";
|
||||
|
||||
// 2. CONSTRUIR PREFIJO
|
||||
let prefijo = "";
|
||||
if (esEspecial) {
|
||||
let segundo = "0";
|
||||
const esOrigenSur = estacionesSur.includes(origen);
|
||||
const esDestinoSur = estacionesSur.includes(destino);
|
||||
const esOrigenNorte = estacionesNorte.includes(origen);
|
||||
const esDestinoNorte = estacionesNorte.includes(destino);
|
||||
const esLAV = (origen === "Genzor" || origen === "Castañeda") && (destino === "Genzor" || destino === "Castañeda");
|
||||
|
||||
if (ultHora) segundo = "0";
|
||||
else if (esLAV) segundo = "7";
|
||||
else if (esOrigenSur && esDestinoSur) segundo = "8";
|
||||
else if (esOrigenNorte && esDestinoNorte) segundo = "9";
|
||||
|
||||
prefijo = `9${segundo}`;
|
||||
} else {
|
||||
// Regulares Mercancías
|
||||
const p1 = operador === "Renfe Mercancías" ? "5" : (operador === "Privada" ? "6" : "8");
|
||||
const p2 = estacionesNorte.includes(origen) ? "9" : "8";
|
||||
const p3 = estacionesNorte.includes(destino) ? "9" : "8";
|
||||
prefijo = `${p1}${p2}${p3}`;
|
||||
}
|
||||
|
||||
// 3. CÁLCULO DEL NÚMERO (+1 y Paridad)
|
||||
const tipoMem = esEspecial ? 'esp' : 'reg';
|
||||
let base = getUltimo(tipoMem) + 1;
|
||||
|
||||
const ajustarParidad = (num, paridad) => {
|
||||
if (paridad === "PAR") return num % 2 === 0 ? num : num + 1;
|
||||
return num % 2 !== 0 ? num : num + 1;
|
||||
};
|
||||
|
||||
let numeroFinal = ajustarParidad(base, paridadRequerida);
|
||||
saveUltimo(tipoMem, numeroFinal);
|
||||
|
||||
// 4. FORMATO FINAL
|
||||
let strFinal = `${prefijo}${String(numeroFinal).slice(-2)}`;
|
||||
if (esEspecial) strFinal = `${prefijo}${String(numeroFinal).slice(-3)}`;
|
||||
|
||||
if (inversion) {
|
||||
const paridadInversa = paridadRequerida === "PAR" ? "IMPAR" : "PAR";
|
||||
setResultado(`${strFinal} / ${paridadInversa}`);
|
||||
} else {
|
||||
setResultado(strFinal);
|
||||
}
|
||||
};
|
||||
|
||||
const resetMemoria = () => {
|
||||
if(confirm("¿Resetear todos los contadores?")) {
|
||||
localStorage.clear();
|
||||
setResultado("-----");
|
||||
}
|
||||
};
|
||||
|
||||
const estilos = {
|
||||
main: { display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', backgroundColor: '#050505', color: '#fff', fontFamily: 'monospace' },
|
||||
card: { backgroundColor: '#111', padding: '30px', borderRadius: '4px', width: '100%', maxWidth: '550px', border: '1px solid #333', boxShadow: '0 0 20px #000' },
|
||||
nav: { display: 'flex', justifyContent: 'space-between', borderBottom: '1px solid #222', paddingBottom: '10px', marginBottom: '20px' },
|
||||
btnMenu: (col) => ({ width: '100%', padding: '15px', margin: '5px 0', border: `1px solid \${col}`, color: col, backgroundColor: 'transparent', cursor: 'pointer', fontWeight: 'bold' }),
|
||||
select: { width: '100%', padding: '12px', margin: '8px 0', backgroundColor: '#1a1a1a', color: '#00d1b2', border: '1px solid #333', outline: 'none' },
|
||||
display: { fontSize: '3.5rem', textAlign: 'center', padding: '20px', color: '#00d1b2', border: '2px solid #00d1b2', margin: '20px 0', backgroundColor: '#000', letterSpacing: '4px' },
|
||||
footer: { display: 'flex', justifyContent: 'space-between', marginTop: '20px', fontSize: '0.7rem', color: '#444' }
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={estilos.main}>
|
||||
<div style={estilos.card}>
|
||||
{pagina === 'menu' ? (
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<h1 style={{color: '#00d1b2', letterSpacing: '5px'}}>AGER v0.2</h1>
|
||||
<p style={{color: '#444', marginBottom: '30px'}}>SISTEMA DE PETICIÓN DE SURCOS</p>
|
||||
<button style={estilos.btnMenu('#00d1b2')} onClick={() => setPagina('especiales')}>[ ESPECIALES 9XXXX ]</button>
|
||||
<button style={estilos.btnMenu('#3498db')} onClick={() => setPagina('regulares')}>[ REGULARES MERC. ]</button>
|
||||
<button style={{...estilos.btnMenu('#555'), marginTop: '40px', fontSize: '0.7rem'}} onClick={resetMemoria}>RESET TOTAL MEMORIA</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={estilos.nav}>
|
||||
<span style={{color: pagina === 'especiales' ? '#00d1b2' : '#3498db'}}>MODO_{pagina.toUpperCase()}</span>
|
||||
<span style={{cursor: 'pointer'}} onClick={() => {setPagina('menu'); setResultado('-----');}}>VOLVER_AL_MENU</span>
|
||||
</div>
|
||||
|
||||
<label style={{fontSize: '0.7rem'}}>PUNTO DE ORIGEN</label>
|
||||
<select style={estilos.select} value={filtros.origen} onChange={(e) => setFiltros({...filtros, origen: e.target.value})}>
|
||||
<option value="">-- SELECCIONAR --</option>
|
||||
{todasLasEstaciones.map(e => <option key={e} value={e}>{e}</option>)}
|
||||
</select>
|
||||
|
||||
<label style={{fontSize: '0.7rem'}}>PUNTO DE DESTINO</label>
|
||||
<select style={estilos.select} value={filtros.destino} onChange={(e) => setFiltros({...filtros, destino: e.target.value})}>
|
||||
<option value="">-- SELECCIONAR --</option>
|
||||
{todasLasEstaciones.map(e => <option key={e} value={e}>{e}</option>)}
|
||||
</select>
|
||||
|
||||
<div style={{backgroundColor: '#161616', padding: '15px', borderRadius: '4px', marginTop: '10px'}}>
|
||||
<label style={{display: 'flex', alignItems: 'center', gap: '10px', cursor: 'pointer', fontSize: '0.8rem', color: '#ccc'}}>
|
||||
<input type="checkbox" checked={filtros.inversion} onChange={(e) => setFiltros({...filtros, inversion: e.target.checked})} />
|
||||
INVERSIÓN DE MARCHA (Doble Paridad)
|
||||
</label>
|
||||
|
||||
{pagina === 'especiales' ? (
|
||||
<label style={{display: 'flex', alignItems: 'center', gap: '10px', cursor: 'pointer', fontSize: '0.8rem', color: '#00d1b2', marginTop: '10px'}}>
|
||||
<input type="checkbox" onChange={(e) => setFiltros({...filtros, ultHora: e.target.checked})} />
|
||||
CANAL ÚLTIMA HORA (Prefijo 90)
|
||||
</label>
|
||||
) : (
|
||||
<select style={{...estilos.select, marginTop: '10px'}} onChange={(e) => setFiltros({...filtros, operador: e.target.value})}>
|
||||
<option value="Renfe Mercancías">Renfe Mercancías (5)</option>
|
||||
<option value="Privada">Empresa Privada (6)</option>
|
||||
<option value="Otra">Otra Operadora (8)</option>
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
style={{...estilos.btnMenu(pagina === 'especiales' ? '#00d1b2' : '#3498db'), backgroundColor: '#eee', color: '#000', marginTop: '20px'}}
|
||||
onClick={() => generarPeticion(pagina === 'especiales')}
|
||||
>
|
||||
GENERAR Y REGISTRAR
|
||||
</button>
|
||||
|
||||
<div style={estilos.display}>{resultado}</div>
|
||||
|
||||
<div style={estilos.footer}>
|
||||
<span>STATUS: ONLINE</span>
|
||||
<span>SISTEMA DE CIRCULACIÓN CGL</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
src/main.jsx
Normal file
9
src/main.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
10
vite.config.js
Normal file
10
vite.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user