Dockerfile:
# Usar una imagen base de Python
FROM python:3.9-slim

# Establecer el directorio de trabajo
WORKDIR /app

# Copiar el archivo requirements.txt e instalar las dependencias
COPY requirements.txt .
RUN pip install -r requirements.txt

# Crear el directorio 'uploads' y ajustar permisos
RUN mkdir uploads && chmod 777 uploads

# Crear el directorio 'matplotlib' y ajustar permisos
RUN mkdir matplotlib && chmod 777 matplotlib

ENV MPLCONFIGDIR=/matplotlib


# Montar el secreto y escribirlo en config.py
RUN --mount=type=secret,id=HUGGING_FACE_PASS,mode=0444,required=true \
    echo "pass_var = '$(cat /run/secrets/HUGGING_FACE_PASS)'" > config.py

# Copiar el contenido de la aplicación al contenedor
COPY . .

# Exponer el puerto 8000
EXPOSE 8000

# Configurar el comando de inicio
CMD ["python", "app.py"]

app.py
from flask import Flask, request, jsonify, render_template, redirect, url_for
import json
import os
import time
import subprocess
import logging 
from config import pass_var  # Importar la variable de configuración


app = Flask(__name__)

# Configuración de rutas de subida
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# Asegúrate de que el directorio de subida exista
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

# Configuración de directorio de matplotlib
MATPLOTLIB_FOLDER = 'matplotlib'
app.config['MATPLOTLIB_FOLDER'] = MATPLOTLIB_FOLDER

# Asegúrate de que el directorio exista
if not os.path.exists(MATPLOTLIB_FOLDER):
    os.makedirs(MATPLOTLIB_FOLDER)
    
def get_cpu_usage_percentage():
    try:
        with open('/sys/fs/cgroup/cpu/cpuacct.usage') as f:
            total_cpu_time_1 = float(f.read().strip())
        
        system_time_1 = time.time()  # Tiempo global en el sistema
        time.sleep(1)
        
        with open('/sys/fs/cgroup/cpu/cpuacct.usage') as f:
            total_cpu_time_2 = float(f.read().strip())
        
        system_time_2 = time.time()  # Tiempo global en el sistema

        cpu_delta = (total_cpu_time_2 - total_cpu_time_1)
        system_delta = (system_time_2 - system_time_1) * 1e9  # Convertir a nanosegundos

        cpu_usage_percentage = (cpu_delta / system_delta) * 100
        if cpu_usage_percentage > 100:
            cpu_usage_percentage = 100
        
        return cpu_usage_percentage
    except FileNotFoundError:
        return "Error al leer el uso de CPU."

def get_memory_usage():
    try:
        with open('/sys/fs/cgroup/memory/memory.usage_in_bytes') as f:
            memory_usage = int(f.read().strip()) / 1048576  # Convertir a MB
        with open('/sys/fs/cgroup/memory/memory.limit_in_bytes') as f:
            memory_limit = int(f.read().strip()) / 1048576  # Convertir a MB

        memory_percentage = (memory_usage / memory_limit) * 100
        return {
            'limit': memory_limit,
            'usage': memory_usage,
            'percentage': memory_percentage
        }
    except FileNotFoundError:
        return "Error al leer el uso de memoria."

@app.route('/')
def index():
    return render_template('welcome.html')
    
@app.route('/pythonc')
def pythonc():
    return render_template('pythonc.html')
    
@app.route('/usage')
def usage():
    return render_template('usage.html')

@app.route('/usageapi', methods=['GET'])
def get_usage():
    cpu_usage_percentage = get_cpu_usage_percentage()
    memory_info = get_memory_usage()
    
    if isinstance(cpu_usage_percentage, str) or isinstance(memory_info, str):
        return jsonify({"error": "No se pudieron obtener los datos de uso de recursos."})

    data = {
        'cpu_usage_percentage': cpu_usage_percentage,
        'memory_usage_mb': memory_info['usage'],
        'memory_limit_mb': memory_info['limit'],
        'memory_percentage': memory_info['percentage']
    }
    return jsonify(data)




@app.route('/create_python_script', methods=['POST'])
def create_python_script():
    try:
        # Obtener los datos JSON de la solicitud
        data = request.get_json()
        # Extraer el nombre, contenido y la contraseña del JSON
        script_name = data.get('name', '')
        script_content = data.get('script', '')
        password = data.get('password', '')  # Obtener la contraseña del JSON
        
        # Validar que se proporcionen el nombre y el contenido del script
        if not script_name or not script_content:
            return jsonify({'error': 'El nombre y el contenido del script son obligatorios.'}), 400
        
        # Validar la contraseña
        if password != pass_var:
            return jsonify({'error': 'Contraseña incorrecta.'}), 403  # Forbidden
        
        # Ruta del script a crear
        script_path = os.path.join(app.config['UPLOAD_FOLDER'], f'{script_name}.py')
        
        # Intentar crear el script
        try:
            with open(script_path, 'w') as f:
                f.write(script_content)
        except IOError as e:
            logging.error(f"Error al escribir el archivo: {str(e)}")
            return jsonify({'error': f"Error al escribir el archivo: {str(e)}"}), 500
        except Exception as e:
            logging.error(f"Error al crear el script: {str(e)}")
            return jsonify({'error': f"Error al crear el script: {str(e)}"}), 500
        
        return jsonify({'message': f'Script {script_name} creado con éxito'}), 201
    
    except Exception as e:
        logging.error(f"Error en la solicitud: {str(e)}")
        return jsonify({'error': f"Error en la solicitud: {str(e)}"}), 500



@app.route('/run_python_script', methods=['POST'])
def run_python_script():
    data = request.get_json()
    script_name = data.get('name', '')

    script_path = os.path.join(app.config['UPLOAD_FOLDER'], f'{script_name}.py')

    if not os.path.exists(script_path):
        return jsonify({'error': 'El script no existe'}), 400

    result = subprocess.run(['python', script_path], capture_output=True, text=True)
    
    return jsonify({
        'output': result.stdout,
        'errors': result.stderr
    })

@app.route('/compute', methods=['POST'])
def compute():
    try:
        data = request.get_json()
        
        # Obtener el nombre del archivo desde 'requestcompute'
        script_name = data.pop('requestcompute', '')
        script_path = os.path.join(app.config['UPLOAD_FOLDER'], f'{script_name}.py')
        
        if not os.path.exists(script_path):
            return jsonify({'error': f'El script {script_name} no existe'}), 400
        
        # Pasar el resto del JSON como una variable al script Python
        json_params = json.dumps(data)
        
        result = subprocess.run(
            ['python', script_path, json_params],
            capture_output=True,
            text=True
        )
        
        return jsonify({
            'output': result.stdout,
            'errors': result.stderr
        })
    except Exception as e:
        logging.error(f"Error al ejecutar el script: {str(e)}")
        return jsonify({'error': f"Error al ejecutar el script: {str(e)}"}), 500



@app.route('/echo', methods=['POST'])
def echo():
    data = request.get_json()
    return jsonify(data)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

optimize.py (código que app.py ejecuta)
import os
import sys
import json
import numpy as np
from shapely.geometry import Polygon, Point

# Establecer un directorio configurado para Matplotlib
os.environ['MPLCONFIGDIR'] = '/matplotlib'

import matplotlib.pyplot as plt

# Variables globales
mesa_ancho, mesa_largo = 2, 1
inversor_ancho, inversor_largo = 3, 2
max_distance = 30
min_distance = 10
max_filas_por_columna = 10
espacio_columna = mesa_ancho + 1
espacio_fila = mesa_largo + 2
desviacion_lateral = 2

def generar_posiciones_columna(col_index, max_filas, inversor_pos, half_columnas):
    positions = []
    for row in range(max_filas):
        pos_arriba = np.array([
            inversor_pos[0] + col_index * espacio_columna,
            inversor_pos[1] + row * espacio_fila + inversor_largo + 10
        ])
        pos_abajo = np.array([
            inversor_pos[0] + col_index * espacio_columna,
            inversor_pos[1] - (row + 1) * espacio_fila
        ])
        if min_distance <= np.linalg.norm(pos_arriba - inversor_pos) <= max_distance:
            if col_index < half_columnas and np.linalg.norm(pos_arriba - np.array([inversor_pos[0] + (col_index + 1) * espacio_columna, pos_arriba[1]])) <= desviacion_lateral:
                pos_arriba[0] += desviacion_lateral
            positions.append(pos_arriba)

        if min_distance <= np.linalg.norm(pos_abajo - inversor_pos) <= max_distance:
            if col_index < half_columnas and np.linalg.norm(pos_abajo - np.array([inversor_pos[0] + (col_index + 1) * espacio_columna, pos_abajo[1]])) <= desviacion_lateral:
                pos_abajo[0] += desviacion_lateral
            positions.append(pos_abajo)
    return np.array(positions)

def esta_dentro_del_poligono(punto, poligono):
    return poligono.contains(Point(punto))

def main():
    try:
        if len(sys.argv) > 1:
            json_params = sys.argv[1]
            data = json.loads(json_params)

            if 'geojson' not in data:
                raise ValueError("El objeto JSON proporcionado no contiene la clave 'geojson'.")

            geojson = data['geojson']

            if 'geometry' not in geojson:
                raise ValueError("El objeto GeoJSON no contiene la clave 'geometry'.")

            scaling_factor = 10000
            polygon_coords = [(lon * scaling_factor, lat * scaling_factor) for lon, lat in geojson['geometry']['coordinates'][0]]
            polygon = Polygon(polygon_coords)

            np.random.seed(42)
            num_mesas = 160
            mesas_pos = np.zeros((num_mesas, 2))

            inversor_pos = np.array([polygon.centroid.x, polygon.centroid.y])

            total_columnas = (num_mesas + max_filas_por_columna - 1) // max_filas_por_columna
            half_columnas = total_columnas // 2

            all_positions = []
            for col in range(-half_columnas, half_columnas + 1):
                column_positions = generar_posiciones_columna(col, max_filas_por_columna, inversor_pos, half_columnas)
                all_positions.extend(column_positions)

            mesas_pos = np.array([pos for pos in all_positions if esta_dentro_del_poligono(pos, polygon)][:num_mesas])

            features = []
            for i, pos in enumerate(mesas_pos):
                x, y = pos
                x /= scaling_factor
                y /= scaling_factor
                coordinates = [
                    [x, y],
                    [x + mesa_ancho / scaling_factor, y],
                    [x + mesa_ancho / scaling_factor, y + mesa_largo / scaling_factor],
                    [x, y + mesa_largo / scaling_factor],
                    [x, y]
                ]

                feature = {
                    "type": "Feature",
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [coordinates]
                    },
                    "properties": {
                        "id": f"Mesa {i + 1}"
                    }
                }
                features.append(feature)

            geojson_output = {
                "type": "FeatureCollection",
                "features": features
            }
            sys.stdout.write(json.dumps(geojson_output, separators=(',', ':')))

            plt.figure(figsize=(10, 10))
            for mesa in mesas_pos:
                rect = plt.Rectangle(mesa / scaling_factor, mesa_ancho, mesa_largo, color='blue', alpha=0.5)
                plt.gca().add_patch(rect)

            rect = plt.Rectangle(inversor_pos / scaling_factor, inversor_ancho, inversor_largo, color='red', alpha=0.5)
            plt.gca().add_patch(rect)

            plt.xlim(0, 50)
            plt.ylim(0, 80)
            plt.xlabel('X')
            plt.ylabel('Y')
            plt.title('Distribución de Mesas e Inversor')
            plt.grid(True)
            plt.savefig('distribucion_mesas_inversor.png')
            plt.show()

        else:
            sys.stdout.write("No se recibieron datos.")

    except Exception as e:
        sys.stdout.write(f"Error al procesar los datos: {e}")
        if 'data' in locals():
            sys.stdout.write(f"Datos recibidos: {data}")

if __name__ == "__main__":
    main()