From 8c4d0d4d0600ca3edbbed657e7a290a8da49a48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Vidal?= Date: Sun, 22 Feb 2026 13:35:16 +0100 Subject: [PATCH] Version CGL --- Dockerfile | 14 ++++ control.sh | 90 ++++++++++++++++++++++ docker-compose.yml | 7 ++ index.html | 12 +++ package.json | 19 +++++ src/App.jsx | 184 +++++++++++++++++++++++++++++++++++++++++++++ src/main.jsx | 9 +++ vite.config.js | 10 +++ 8 files changed, 345 insertions(+) create mode 100644 Dockerfile create mode 100644 control.sh create mode 100644 docker-compose.yml create mode 100644 index.html create mode 100644 package.json create mode 100644 src/App.jsx create mode 100644 src/main.jsx create mode 100644 vite.config.js diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..da41417 --- /dev/null +++ b/Dockerfile @@ -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;"] diff --git a/control.sh b/control.sh new file mode 100644 index 0000000..9a4c6a5 --- /dev/null +++ b/control.sh @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4b61b54 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + frontend: + build: . + container_name: creapeticiones_frontend + ports: + - "3000:80" + restart: always diff --git a/index.html b/index.html new file mode 100644 index 0000000..a07ac65 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Creapeticiones | Generador + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..d99004b --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..ba08a7a --- /dev/null +++ b/src/App.jsx @@ -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 ( +
+
+ {pagina === 'menu' ? ( +
+

AGER v0.2

+

SISTEMA DE PETICIÓN DE SURCOS

+ + + +
+ ) : ( +
+
+ MODO_{pagina.toUpperCase()} + {setPagina('menu'); setResultado('-----');}}>VOLVER_AL_MENU +
+ + + + + + + +
+ + + {pagina === 'especiales' ? ( + + ) : ( + + )} +
+ + + +
{resultado}
+ +
+ STATUS: ONLINE + SISTEMA DE CIRCULACIÓN CGL +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..51a8c58 --- /dev/null +++ b/src/main.jsx @@ -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( + + + , +) diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..784d3bf --- /dev/null +++ b/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + host: true, + port: 3000 + } +})