From dac9b502d468501802d453a333215dd4cd90941b Mon Sep 17 00:00:00 2001 From: luisgulo Date: Tue, 21 Jul 2026 14:27:44 +0200 Subject: [PATCH] Version 1.2 Totalmente operativa --- Dockerfile | 5 + app.py | 212 ++++++++++++++++++ build.sh | 2 + .../01-Publicaciones_Soloconlinux.encuesta | 17 ++ encuestas/02-Eleccion servidor.encuesta | 19 ++ plantilla.encuesta | 20 ++ run.sh | 12 + stop.sh | 5 + 8 files changed, 292 insertions(+) create mode 100644 Dockerfile create mode 100644 app.py create mode 100755 build.sh create mode 100644 encuestas/01-Publicaciones_Soloconlinux.encuesta create mode 100644 encuestas/02-Eleccion servidor.encuesta create mode 100644 plantilla.encuesta create mode 100755 run.sh create mode 100755 stop.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4ae3ba7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.11-slim +WORKDIR /app +RUN pip install --no-cache-dir flask +EXPOSE 8080 +CMD ["python", "app.py"] diff --git a/app.py b/app.py new file mode 100644 index 0000000..3f2f61f --- /dev/null +++ b/app.py @@ -0,0 +1,212 @@ +import os +import configparser +from flask import Flask, request, render_template_string, redirect, url_for + +app = Flask(__name__) +DATA_DIR = "/data" + +def get_poll_files(): + if not os.path.exists(DATA_DIR): + return [] + return [f for f in os.listdir(DATA_DIR) if f.endswith('.encuesta')] + +def load_poll(filename): + filepath = os.path.join(DATA_DIR, filename) + if not os.path.exists(filepath): + return None + + config = configparser.ConfigParser() + config.read(filepath, encoding='utf-8') + + poll_id = filename.replace('.encuesta', '') + titulo = config.get('encuesta', 'titulo', fallback=poll_id) + pregunta = config.get('encuesta', 'pregunta', fallback='') + + # Leer el estado de la encuesta (por defecto True si no existe el campo) + activa_raw = config.get('encuesta', 'activa', fallback='true').lower() + activa = activa_raw in ['true', '1', 'yes', 'si'] + + options = [] + votes = [] + if 'opciones' in config and 'votos' in config: + for key in config['opciones']: + options.append(config['opciones'][key]) + votes.append(int(config['votos'].get(key, '0'))) + + return { + "id": poll_id, + "filename": filename, + "titulo": titulo, + "pregunta": pregunta, + "activa": activa, + "options": options, + "votes": votes + } + +def save_vote(filename, option_index): + filepath = os.path.join(DATA_DIR, filename) + config = configparser.ConfigParser() + config.read(filepath, encoding='utf-8') + + # Seguridad: verificar si sigue activa antes de guardar + activa_raw = config.get('encuesta', 'activa', fallback='true').lower() + if activa_raw not in ['true', '1', 'yes', 'si']: + return False + + key = str(option_index + 1) + current_votes = int(config.get('votos', key, fallback='0')) + config.set('votos', key, str(current_votes + 1)) + + with open(filepath, 'w', encoding='utf-8') as f: + config.write(f) + return True + +HTML_LAYOUT = """ + + + + + + Encuestas + + + +
+ {{ content | safe }} +
+ + +""" + +@app.route('/') +def home(): + files = get_poll_files() + active_polls = [] + closed_polls = [] + + for f in files: + poll = load_poll(f) + if poll: + if poll["activa"]: + active_polls.append(poll) + else: + closed_polls.append(poll) + + content = "

SoloConLinux: Panel de Encuestas

" + + # BLOQUE: Encuestas Activas + content += '
Encuestas Activas
' + if not active_polls: + content += "

No hay encuestas activas en este momento.

" + else: + for poll in active_polls: + content += f''' +
+ ACTIVA
+ {poll["titulo"]}
+ {poll["pregunta"]}

+ Votar / Resultados +
+ ''' + + # BLOQUE: Encuestas Finalizadas + content += '
Encuestas Finalizadas
' + if not closed_polls: + content += "

No hay encuestas finalizadas.

" + else: + for poll in closed_polls: + content += f''' +
+ FINALIZADA
+ {poll["titulo"]}
+ {poll["pregunta"]}

+ Ver Resultados +
+ ''' + + return render_template_string(HTML_LAYOUT, content=content) + +@app.route('/poll/') +def show_poll(poll_id): + filename = f"{poll_id}.encuesta" + poll = load_poll(filename) + if not poll: + return "Encuesta no encontrada", 404 + + # Si la encuesta NO está activa, forzar siempre la vista de resultados + voted = (request.args.get('voted') == '1') or (not poll["activa"]) + + content = f'

{poll["pregunta"]}

' + + if not poll["activa"]: + content += '

⚠️ Esta encuesta ha finalizado. Solo se pueden consultar los resultados.

' + + if voted: + total = sum(poll["votes"]) + content += "

Resultados:

" + bar_class = "bar" if poll["activa"] else "bar bar-closed" + + for i, opt in enumerate(poll["options"]): + count = poll["votes"][i] + percent = round((count / total * 100), 1) if total > 0 else 0 + content += f''' +
+
{opt}
+
+
+ {count} votos ({percent}%) +
+
+ ''' + content += f'

Total de votos: {total}

' + content += f'Volver al listado' + else: + content += f'
' + for i, opt in enumerate(poll["options"]): + content += f''' +
+ +
+ ''' + content += '' + content += f'

Ver resultados sin votar' + content += '
' + + return render_template_string(HTML_LAYOUT, content=content) + +@app.route('/vote/', methods=['POST']) +def vote(poll_id): + filename = f"{poll_id}.encuesta" + poll = load_poll(filename) + + # Si la encuesta está desactivada, ignorar el intento de voto + if not poll or not poll["activa"]: + return redirect(url_for('show_poll', poll_id=poll_id)) + + idx = int(request.form.get('option')) + save_vote(filename, idx) + return redirect(url_for('show_poll', poll_id=poll_id, voted=1)) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..496523c --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker build -t encuestas . diff --git a/encuestas/01-Publicaciones_Soloconlinux.encuesta b/encuestas/01-Publicaciones_Soloconlinux.encuesta new file mode 100644 index 0000000..957f738 --- /dev/null +++ b/encuestas/01-Publicaciones_Soloconlinux.encuesta @@ -0,0 +1,17 @@ +[encuesta] +titulo = Publicaciones en SoloConLinux +pregunta = ¿Qué opináis sobre crear este tipo de publicación en SoloConLinux? +activa = true + +[opciones] +1 = Es algo interesante +2 = Interesante pero mejor 2 ediciones anuales +3 = No tiene sentido hoy en día +4 = Prefiero directamente el Blog de soloconlinux.org.es + +[votos] +1 = 3 +2 = 1 +3 = 0 +4 = 1 + diff --git a/encuestas/02-Eleccion servidor.encuesta b/encuestas/02-Eleccion servidor.encuesta new file mode 100644 index 0000000..4674956 --- /dev/null +++ b/encuestas/02-Eleccion servidor.encuesta @@ -0,0 +1,19 @@ +[encuesta] +titulo = Elección de distribución en servidores +pregunta = ¿Qué distribución de Linux prefieres utilizar para desplegar servicios en producción? +activa = false + +[opciones] +1 = Debian (Estable y puro) +2 = Ubuntu Server (Soporte extendido y ecosistema) +3 = AlmaLinux / Rocky Linux (Entorno empresarial tipo RHEL) +4 = Alpine Linux (Imágenes ultra ligeras para contenedores) +5 = Arch Linux (Para estar siempre a la última) + +[votos] +1 = 1324 +2 = 340 +3 = 564 +4 = 340 +5 = 450 + diff --git a/plantilla.encuesta b/plantilla.encuesta new file mode 100644 index 0000000..ac7c1fc --- /dev/null +++ b/plantilla.encuesta @@ -0,0 +1,20 @@ +[encuesta] +titulo = Titulo de la Encuesta +pregunta = ¿Qué bla bla bla bla prefieres? +# activa = true|false +activa = true + +[opciones] +# n = texto de la opcion +1 = Texto de la opcion 1 +2 = Texto de la opcion 2 +3 = Texto de la opcion 3 +4 = Texto de la opcion 4 + +[votos] +# inicializar con valor 0 +1 = 0 +2 = 0 +3 = 0 +4 = 0 + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0930e07 --- /dev/null +++ b/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +docker rm -f encuestas 2> /dev/null + +docker run -d \ + --name encuestas \ + --restart always \ + -p 8080:8080 \ + -v ${PWD}/app.py:/app/app.py \ + -v ${PWD}/encuestas:/data \ + encuestas + +echo "Abra el Gestor de Encuestas en: http://localhost:8080" diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..882109a --- /dev/null +++ b/stop.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "Deteniendo contenedor de Encuestas..." +docker rm -f encuestas 2> /dev/null +echo "¡Detenido!" +echo