| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- from flask import Blueprint, render_template, request, redirect, url_for, flash, session
- from models import db, CatalogoPaneles, DatosSolaresCiudad, ProyectosUsuario, Casa, Configuracion
- from datetime import datetime
- from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
- from extensions import socketio, login_manager
- import paramiko
- import threading
- import time
- import os
- main = Blueprint('main', __name__)
- # --- Auth Setup ---
- class User(UserMixin):
- def __init__(self, id):
- self.id = id
- @login_manager.user_loader
- def load_user(user_id):
- if user_id == '1':
- return User(id='1')
- return None
- @main.route('/login', methods=['GET', 'POST'])
- def login():
- if request.method == 'POST':
- username = request.form.get('username')
- password = request.form.get('password')
- if username == 'admin' and password == 'solaradmin':
- user = User(id='1')
- login_user(user)
- return redirect(url_for('main.admin'))
- else:
- flash('Credenciales inválidas', 'danger')
- return render_template('login.html')
- @main.route('/logout')
- @login_required
- def logout():
- logout_user()
- return redirect(url_for('main.index'))
- @main.route('/admin')
- @login_required
- def admin():
- return render_template('admin.html')
- # --- SSH Logic ---
- ssh_sessions = {}
- @socketio.on('connect', namespace='/ssh')
- def connect_ssh():
- if not current_user.is_authenticated:
- return False
- # Start a new SSH session
- # WARNING: Hardcoded credentials for demo purposes. EXTREMELY INSECURE.
- # In a real app, use keys or prompt for credentials.
- # Using the server's own credentials or a specific user.
- # For this demo, we'll try to connect to localhost if possible, or just echo.
- # But the user asked for "SSH console".
- # Let's assume we want to connect to the machine running this app.
- # We need credentials. The user asked for "dialogo que indique credenciales".
- # So we will expect the user to provide them via the UI, but for the initial connection
- # we might need to wait for a 'start_ssh' event with credentials.
- pass
- @socketio.on('start_ssh', namespace='/ssh')
- def start_ssh(data):
- if not current_user.is_authenticated:
- return
-
- host = data.get('host', 'localhost')
- port = int(data.get('port', 22))
- username = data.get('username')
- password = data.get('password')
-
- sid = request.sid
-
- try:
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- client.connect(host, port=port, username=username, password=password)
-
- channel = client.invoke_shell()
- ssh_sessions[sid] = {'client': client, 'channel': channel}
-
- def listen_to_ssh():
- while True:
- if channel.recv_ready():
- data = channel.recv(1024).decode('utf-8')
- socketio.emit('ssh_output', {'data': data}, room=sid, namespace='/ssh')
- socketio.sleep(0.1)
- if not channel.active:
- break
-
- socketio.start_background_task(listen_to_ssh)
-
- socketio.emit('ssh_status', {'status': 'connected'}, room=sid, namespace='/ssh')
-
- except Exception as e:
- socketio.emit('ssh_status', {'status': 'error', 'message': str(e)}, room=sid, namespace='/ssh')
- @socketio.on('ssh_input', namespace='/ssh')
- def ssh_input(data):
- sid = request.sid
- if sid in ssh_sessions:
- channel = ssh_sessions[sid]['channel']
- channel.send(data['data'])
- @socketio.on('disconnect', namespace='/ssh')
- def disconnect_ssh():
- sid = request.sid
- if sid in ssh_sessions:
- ssh_sessions[sid]['client'].close()
- del ssh_sessions[sid]
- @main.route('/')
- def index():
- return render_template('index.html')
- @main.route('/metodologia')
- def metodologia():
- return render_template('methodology.html')
- @main.route('/configuracion', methods=['GET', 'POST'])
- def configuracion():
- config_precio = Configuracion.query.filter_by(clave='precio_kwh').first()
- ciudades = DatosSolaresCiudad.query.all()
- casas = Casa.query.all()
- paneles = CatalogoPaneles.query.all()
-
- edit_id = request.args.get('edit_id')
- casa_editar = None
- if edit_id:
- casa_editar = Casa.query.get(edit_id)
-
- if request.method == 'POST':
- nuevo_precio = request.form.get('precio_kwh')
- if config_precio:
- config_precio.valor = nuevo_precio
- else:
- config_precio = Configuracion(clave='precio_kwh', valor=nuevo_precio)
- db.session.add(config_precio)
-
- db.session.commit()
- return redirect(url_for('main.configuracion'))
-
- return render_template('configuracion.html',
- precio_kwh=config_precio.valor if config_precio else '0.15',
- ciudades=ciudades,
- casas=casas,
- paneles=paneles,
- casa_editar=casa_editar)
- @main.route('/casa/editar/<int:id_casa>', methods=['POST'])
- def editar_casa(id_casa):
- casa = Casa.query.get_or_404(id_casa)
- casa.nombre = request.form.get('nombre')
- casa.area_techo = float(request.form.get('area_techo'))
- casa.orientacion = request.form.get('orientacion')
- inclinacion_str = request.form.get('inclinacion')
- casa.inclinacion = float(inclinacion_str) if inclinacion_str else 0.0
- casa.id_ciudad = request.form.get('id_ciudad')
-
- db.session.commit()
- flash('Casa actualizada correctamente.', 'success')
- return redirect(url_for('main.configuracion'))
- @main.route('/casa/eliminar/<int:id_casa>', methods=['POST'])
- def eliminar_casa(id_casa):
- casa = Casa.query.get_or_404(id_casa)
-
- # Desvincular proyectos antes de eliminar
- proyectos = ProyectosUsuario.query.filter_by(id_casa=id_casa).all()
- for p in proyectos:
- p.id_casa = None
-
- db.session.delete(casa)
- db.session.commit()
- flash('Casa eliminada correctamente.', 'success')
- return redirect(url_for('main.configuracion'))
- @main.route('/panel/nuevo', methods=['POST'])
- def nuevo_panel():
- marca = request.form.get('marca')
- modelo = request.form.get('modelo')
- eficiencia_r = float(request.form.get('eficiencia_r'))
- area_m2 = float(request.form.get('area_m2'))
- precio_unitario = float(request.form.get('precio_unitario'))
-
- panel = CatalogoPaneles(marca=marca, modelo=modelo, eficiencia_r=eficiencia_r, area_m2=area_m2, precio_unitario=precio_unitario)
- db.session.add(panel)
- db.session.commit()
- flash('Panel agregado correctamente.', 'success')
- return redirect(url_for('main.configuracion'))
- @main.route('/panel/editar/<int:id_panel>', methods=['POST'])
- def editar_panel(id_panel):
- panel = CatalogoPaneles.query.get_or_404(id_panel)
- panel.marca = request.form.get('marca')
- panel.modelo = request.form.get('modelo')
- panel.eficiencia_r = float(request.form.get('eficiencia_r'))
- panel.area_m2 = float(request.form.get('area_m2'))
- panel.precio_unitario = float(request.form.get('precio_unitario'))
-
- db.session.commit()
- flash('Panel actualizado correctamente.', 'success')
- return redirect(url_for('main.configuracion'))
- @main.route('/panel/eliminar/<int:id_panel>', methods=['POST'])
- def eliminar_panel(id_panel):
- panel = CatalogoPaneles.query.get_or_404(id_panel)
-
- # Verificar si hay proyectos usando este panel
- proyectos = ProyectosUsuario.query.filter_by(id_panel=id_panel).first()
- if proyectos:
- flash('No se puede eliminar este panel porque hay proyectos asociados a él.', 'danger')
- return redirect(url_for('main.configuracion'))
-
- db.session.delete(panel)
- db.session.commit()
- flash('Panel eliminado correctamente.', 'success')
- return redirect(url_for('main.configuracion'))
- @main.route('/casas')
- def casas():
- casas = Casa.query.all()
- ciudades = DatosSolaresCiudad.query.all()
-
- return render_template('casas.html', casas=casas, ciudades=ciudades)
- @main.route('/proyectos')
- def proyectos():
- id_casa_filter = request.args.get('id_casa', type=int)
- query = ProyectosUsuario.query
-
- if id_casa_filter:
- query = query.filter_by(id_casa=id_casa_filter)
-
- proyectos = query.order_by(ProyectosUsuario.fecha_referencia.desc()).all()
- casas = Casa.query.all()
-
- # Prepare chart data (reversed for chronological order in chart)
- chart_proyectos = proyectos[::-1]
- labels = [p.fecha_referencia.strftime('%Y-%m-%d') for p in chart_proyectos]
-
- costo_sin_solar = []
- costo_con_solar = []
-
- for p in chart_proyectos:
- costo_actual = p.costo_actual_mensual if p.costo_actual_mensual else 0
- ahorro = p.ahorro_estimado if p.ahorro_estimado else 0
- nuevo_costo = max(0, costo_actual - ahorro)
-
- costo_sin_solar.append(costo_actual)
- costo_con_solar.append(nuevo_costo)
-
- return render_template('proyectos.html',
- proyectos=proyectos,
- casas=casas,
- selected_casa=id_casa_filter,
- labels=labels,
- costo_sin_solar=costo_sin_solar,
- costo_con_solar=costo_con_solar)
- @main.route('/casa/nueva', methods=['POST'])
- def nueva_casa():
- nombre = request.form.get('nombre')
- area_techo = float(request.form.get('area_techo'))
- orientacion = request.form.get('orientacion', 'Norte')
- inclinacion_str = request.form.get('inclinacion')
- inclinacion = float(inclinacion_str) if inclinacion_str else 0.0
- id_ciudad = request.form.get('id_ciudad')
-
- casa = Casa(nombre=nombre, area_techo=area_techo, orientacion=orientacion, inclinacion=inclinacion, id_ciudad=id_ciudad)
- db.session.add(casa)
- db.session.commit()
-
- return redirect(url_for('main.configuracion'))
- @main.route('/simular/<int:id_casa>')
- def simular(id_casa):
- casa = Casa.query.get_or_404(id_casa)
- paneles = CatalogoPaneles.query.all()
- config_precio = Configuracion.query.filter_by(clave='precio_kwh').first()
- precio_kwh = float(config_precio.valor) if config_precio else 0.15
-
- return render_template('simulation.html', casa=casa, paneles=paneles, precio_kwh=precio_kwh)
- @main.route('/calculate', methods=['POST'])
- def calculate():
- id_casa = request.form.get('id_casa')
- id_panel = request.form.get('id_panel')
- cantidad = int(request.form.get('cantidad'))
-
- # Nuevos campos opcionales
- consumo_kwh = request.form.get('consumo_kwh')
-
- consumo_kwh = float(consumo_kwh) if consumo_kwh else 0
-
- # Obtenemos el precio global configurado
- config_precio = Configuracion.query.filter_by(clave='precio_kwh').first()
- precio_kwh = float(config_precio.valor) if config_precio else 0.15
-
- # Calculamos el costo mensual basado en el consumo y el precio global
- costo_mensual = consumo_kwh * precio_kwh
-
- casa = Casa.query.get(id_casa)
- ciudad = casa.ciudad
- panel = CatalogoPaneles.query.get(id_panel)
-
- # E = A * r * H * PR
- # Monthly E = (A_panel * quantity) * r * H * 30 * 0.75
-
- area_total = panel.area_m2 * cantidad
- pr = 0.75
-
- # Factor de corrección por orientación e inclinación
- # Ecuador está en latitud 0, por lo que la inclinación ideal es casi plana (0-10 grados).
- # Si la inclinación es mayor, la orientación empieza a importar más.
- factor_orientacion = 1.0
-
- if casa.inclinacion > 10:
- if casa.orientacion in ['Este', 'Oeste']:
- factor_orientacion = 0.90 # Pérdida por menos horas de sol pico
- elif casa.orientacion in ['Norte', 'Sur']:
- factor_orientacion = 0.95 # Pérdida leve por ángulo solar
- elif casa.orientacion == 'Plano':
- factor_orientacion = 1.0
- else:
- factor_orientacion = 0.92 # Noreste, Sureste, etc.
-
- days = 30
-
- energia_mensual = area_total * panel.eficiencia_r * ciudad.irradiacion_h_promedio * days * pr * factor_orientacion
-
- # Bonus: ROI
- ahorro_mensual = energia_mensual * precio_kwh
-
- # Si el ahorro es mayor que el costo actual (si existe), lo limitamos al costo actual (no puedes ahorrar más de lo que pagas)
- # Aunque técnicamente podrías vender excedentes, para simplificar asumimos net metering o autoconsumo puro.
- if costo_mensual > 0 and ahorro_mensual > costo_mensual:
- ahorro_mensual = costo_mensual
-
- costo_total = panel.precio_unitario * cantidad
- tiempo_recuperacion = (costo_total / ahorro_mensual / 12) if ahorro_mensual > 0 else 0 # Years
-
- # Bonus: CO2
- co2_evitado = energia_mensual * 0.4
- # Nuevo costo estimado
- nuevo_costo_mensual = max(0, costo_mensual - ahorro_mensual) if costo_mensual > 0 else 0
- porcentaje_cobertura = (energia_mensual / consumo_kwh * 100) if consumo_kwh > 0 else 0
-
- return render_template('results.html',
- energia=round(energia_mensual, 2),
- ahorro=round(ahorro_mensual, 2),
- recuperacion=round(tiempo_recuperacion, 1),
- co2=round(co2_evitado, 2),
- ciudad=ciudad,
- panel=panel,
- cantidad=cantidad,
- costo_total=costo_total,
- casa=casa,
- consumo_kwh=consumo_kwh,
- costo_mensual=round(costo_mensual, 2),
- nuevo_costo_mensual=round(nuevo_costo_mensual, 2),
- porcentaje_cobertura=round(porcentaje_cobertura, 1),
- today=datetime.now().strftime('%Y-%m-%d'))
- @main.route('/save', methods=['POST'])
- def save():
- nombre_cliente = request.form.get('nombre_cliente')
- id_casa = request.form.get('id_casa')
- id_panel = request.form.get('id_panel')
- cantidad = request.form.get('cantidad')
- energia = request.form.get('energia')
- ahorro = request.form.get('ahorro')
-
- consumo_kwh = request.form.get('consumo_kwh')
- costo_mensual = request.form.get('costo_mensual')
- fecha_str = request.form.get('fecha_referencia')
-
- casa = Casa.query.get(id_casa)
-
- if fecha_str:
- try:
- fecha_referencia = datetime.strptime(fecha_str, '%Y-%m-%d')
- except ValueError:
- fecha_referencia = datetime.now()
- else:
- fecha_referencia = datetime.now()
-
- proyecto = ProyectosUsuario(
- nombre_cliente=nombre_cliente,
- id_casa=id_casa,
- id_ciudad=casa.id_ciudad,
- id_panel=id_panel,
- cantidad_paneles=cantidad,
- energia_estimada_mensual=energia,
- ahorro_estimado=ahorro,
- consumo_actual_kwh=float(consumo_kwh) if consumo_kwh and float(consumo_kwh) > 0 else None,
- costo_actual_mensual=float(costo_mensual) if costo_mensual and float(costo_mensual) > 0 else None,
- fecha_referencia=fecha_referencia
- )
-
- db.session.add(proyecto)
- db.session.commit()
-
- return redirect(url_for('main.proyectos'))
|