Browse Source

hello world

Matthew Trejo 3 days ago
commit
c66a6bbfbe

+ 17 - 0
.github/copilot-instructions.md

@@ -0,0 +1,17 @@
+- [x] Verify that the copilot-instructions.md file in the .github directory is created.
+
+- [x] Clarify Project Requirements
+
+- [x] Scaffold the Project
+
+- [x] Customize the Project
+
+- [x] Install Required Extensions
+
+- [x] Compile the Project
+
+- [x] Create and Run Task
+
+- [x] Launch the Project
+
+- [x] Ensure Documentation is Complete

+ 14 - 0
.vscode/tasks.json

@@ -0,0 +1,14 @@
+{
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"label": "Run Flask App",
+			"type": "shell",
+			"command": "./venv/bin/python app.py",
+			"args": [],
+			"isBackground": true,
+			"problemMatcher": [],
+			"group": "none"
+		}
+	]
+}

+ 26 - 0
README.md

@@ -0,0 +1,26 @@
+# Calculadora de Viabilidad Solar
+
+Aplicación web en Flask para calcular la viabilidad de proyectos de energía solar fotovoltaica.
+
+## Inicio Rápido
+
+1. **Configurar entorno:**
+   ```bash
+   python -m venv venv
+   source venv/bin/activate  # Linux/Mac
+   # venv\Scripts\activate   # Windows
+   pip install -r requirements.txt
+   ```
+
+2. **Ejecutar:**
+   ```bash
+   python app.py
+   ```
+   Accede a `http://127.0.0.1:5000`.0,15
+
+## Estructura
+
+- `app.py`: Aplicación principal.
+- `models.py`: Modelos de datos.
+- `routes.py`: Rutas.
+- `templates/`: Vistas HTML.

BIN
__pycache__/app.cpython-313.pyc


BIN
__pycache__/models.cpython-313.pyc


BIN
__pycache__/routes.cpython-313.pyc


+ 20 - 0
app.py

@@ -0,0 +1,20 @@
+from flask import Flask
+from models import db
+
+def create_app():
+    app = Flask(__name__)
+    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///solarcalc.db'
+    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+
+    db.init_app(app)
+
+    with app.app_context():
+        from routes import main
+        app.register_blueprint(main)
+        db.create_all()
+
+    return app
+
+if __name__ == '__main__':
+    app = create_app()
+    app.run(debug=True)

BIN
instance/solarcalc.db


+ 45 - 0
models.py

@@ -0,0 +1,45 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+
+class CatalogoPaneles(db.Model):
+    __tablename__ = 'catalogo_paneles'
+    id_panel = db.Column(db.Integer, primary_key=True)
+    marca = db.Column(db.String(50), nullable=False)
+    modelo = db.Column(db.String(50), nullable=False)
+    eficiencia_r = db.Column(db.Float, nullable=False)
+    area_m2 = db.Column(db.Float, nullable=False)
+    precio_unitario = db.Column(db.Float, nullable=False)
+
+class DatosSolaresCiudad(db.Model):
+    __tablename__ = 'datos_solares_ciudad'
+    id_ciudad = db.Column(db.Integer, primary_key=True)
+    nombre_ciudad = db.Column(db.String(100), nullable=False)
+    provincia = db.Column(db.String(100), nullable=False)
+    irradiacion_h_promedio = db.Column(db.Float, nullable=False)
+
+class Casa(db.Model):
+    __tablename__ = 'casas'
+    id_casa = db.Column(db.Integer, primary_key=True)
+    nombre = db.Column(db.String(100), nullable=False)
+    area_techo = db.Column(db.Float, nullable=False) # Área disponible en m2
+    id_ciudad = db.Column(db.Integer, db.ForeignKey('datos_solares_ciudad.id_ciudad'), nullable=False)
+    
+    ciudad = db.relationship('DatosSolaresCiudad', backref=db.backref('casas', lazy=True))
+
+class ProyectosUsuario(db.Model):
+    __tablename__ = 'proyectos_usuario'
+    id_proyecto = db.Column(db.Integer, primary_key=True)
+    nombre_cliente = db.Column(db.String(100), nullable=False)
+    # Vinculamos opcionalmente a una casa
+    id_casa = db.Column(db.Integer, db.ForeignKey('casas.id_casa'), nullable=True)
+    id_ciudad = db.Column(db.Integer, db.ForeignKey('datos_solares_ciudad.id_ciudad'), nullable=False)
+    id_panel = db.Column(db.Integer, db.ForeignKey('catalogo_paneles.id_panel'), nullable=False)
+    cantidad_paneles = db.Column(db.Integer, nullable=False)
+    energia_estimada_mensual = db.Column(db.Float, nullable=False)
+    ahorro_estimado = db.Column(db.Float, nullable=True)
+    fecha_simulacion = db.Column(db.DateTime, default=db.func.current_timestamp())
+
+    ciudad = db.relationship('DatosSolaresCiudad', backref=db.backref('proyectos', lazy=True))
+    panel = db.relationship('CatalogoPaneles', backref=db.backref('proyectos', lazy=True))
+    casa = db.relationship('Casa', backref=db.backref('proyectos', lazy=True))

+ 2 - 0
requirements.txt

@@ -0,0 +1,2 @@
+Flask
+Flask-SQLAlchemy

+ 96 - 0
routes.py

@@ -0,0 +1,96 @@
+from flask import Blueprint, render_template, request, redirect, url_for
+from models import db, CatalogoPaneles, DatosSolaresCiudad, ProyectosUsuario, Casa
+
+main = Blueprint('main', __name__)
+
+@main.route('/')
+def index():
+    # Ahora la página principal lista las casas
+    casas = Casa.query.all()
+    ciudades = DatosSolaresCiudad.query.all()
+        
+    return render_template('casas.html', casas=casas, ciudades=ciudades)
+
+@main.route('/casa/nueva', methods=['POST'])
+def nueva_casa():
+    nombre = request.form.get('nombre')
+    area_techo = float(request.form.get('area_techo'))
+    id_ciudad = request.form.get('id_ciudad')
+    
+    casa = Casa(nombre=nombre, area_techo=area_techo, id_ciudad=id_ciudad)
+    db.session.add(casa)
+    db.session.commit()
+    
+    return redirect(url_for('main.index'))
+
+@main.route('/simular/<int:id_casa>')
+def simular(id_casa):
+    casa = Casa.query.get_or_404(id_casa)
+    paneles = CatalogoPaneles.query.all()
+        
+    return render_template('simulation.html', casa=casa, paneles=paneles)
+
+@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'))
+    precio_kwh = float(request.form.get('precio_kwh', 0.15))
+    
+    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
+    days = 30
+    
+    energia_mensual = area_total * panel.eficiencia_r * ciudad.irradiacion_h_promedio * days * pr
+    
+    # Bonus: ROI
+    ahorro_mensual = energia_mensual * precio_kwh
+    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
+    
+    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)
+
+@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')
+    
+    casa = Casa.query.get(id_casa)
+    
+    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
+    )
+    
+    db.session.add(proyecto)
+    db.session.commit()
+    
+    return redirect(url_for('main.index'))

+ 71 - 0
seed.py

@@ -0,0 +1,71 @@
+from app import create_app
+from models import db, DatosSolaresCiudad, CatalogoPaneles
+
+def seed_database():
+    app = create_app()
+    with app.app_context():
+        print("Iniciando poblado de base de datos...")
+        
+        # Opcional: Limpiar tablas existentes si quieres reiniciar de cero
+        # db.drop_all()
+        # db.create_all()
+
+        # Cities (Ecuador)
+        if not DatosSolaresCiudad.query.first():
+            print("Creando ciudades...")
+            cities = [
+                # Sierra
+                DatosSolaresCiudad(nombre_ciudad="Quito", provincia="Pichincha", irradiacion_h_promedio=5.1),
+                DatosSolaresCiudad(nombre_ciudad="Cuenca", provincia="Azuay", irradiacion_h_promedio=4.8),
+                DatosSolaresCiudad(nombre_ciudad="Loja", provincia="Loja", irradiacion_h_promedio=5.0),
+                DatosSolaresCiudad(nombre_ciudad="Ambato", provincia="Tungurahua", irradiacion_h_promedio=4.7),
+                DatosSolaresCiudad(nombre_ciudad="Ibarra", provincia="Imbabura", irradiacion_h_promedio=4.9),
+                DatosSolaresCiudad(nombre_ciudad="Riobamba", provincia="Chimborazo", irradiacion_h_promedio=5.2),
+                DatosSolaresCiudad(nombre_ciudad="Tulcán", provincia="Carchi", irradiacion_h_promedio=4.8),
+                DatosSolaresCiudad(nombre_ciudad="Latacunga", provincia="Cotopaxi", irradiacion_h_promedio=5.2),
+                DatosSolaresCiudad(nombre_ciudad="Guaranda", provincia="Bolívar", irradiacion_h_promedio=4.9),
+                DatosSolaresCiudad(nombre_ciudad="Azogues", provincia="Cañar", irradiacion_h_promedio=4.8),
+                
+                # Costa
+                DatosSolaresCiudad(nombre_ciudad="Guayaquil", provincia="Guayas", irradiacion_h_promedio=4.6),
+                DatosSolaresCiudad(nombre_ciudad="Manta", provincia="Manabí", irradiacion_h_promedio=5.3),
+                DatosSolaresCiudad(nombre_ciudad="Portoviejo", provincia="Manabí", irradiacion_h_promedio=5.0),
+                DatosSolaresCiudad(nombre_ciudad="Esmeraldas", provincia="Esmeraldas", irradiacion_h_promedio=4.5),
+                DatosSolaresCiudad(nombre_ciudad="Babahoyo", provincia="Los Ríos", irradiacion_h_promedio=4.4),
+                DatosSolaresCiudad(nombre_ciudad="Machala", provincia="El Oro", irradiacion_h_promedio=4.7),
+                DatosSolaresCiudad(nombre_ciudad="Santa Elena", provincia="Santa Elena", irradiacion_h_promedio=5.5),
+                DatosSolaresCiudad(nombre_ciudad="Santo Domingo", provincia="Santo Domingo de los Tsáchilas", irradiacion_h_promedio=4.2),
+                
+                # Oriente (Amazonía)
+                DatosSolaresCiudad(nombre_ciudad="Nueva Loja", provincia="Sucumbíos", irradiacion_h_promedio=4.0),
+                DatosSolaresCiudad(nombre_ciudad="Puerto Francisco de Orellana", provincia="Orellana", irradiacion_h_promedio=4.1),
+                DatosSolaresCiudad(nombre_ciudad="Tena", provincia="Napo", irradiacion_h_promedio=3.8),
+                DatosSolaresCiudad(nombre_ciudad="Puyo", provincia="Pastaza", irradiacion_h_promedio=3.9),
+                DatosSolaresCiudad(nombre_ciudad="Macas", provincia="Morona Santiago", irradiacion_h_promedio=4.0),
+                DatosSolaresCiudad(nombre_ciudad="Zamora", provincia="Zamora Chinchipe", irradiacion_h_promedio=4.1),
+                
+                # Insular
+                DatosSolaresCiudad(nombre_ciudad="Puerto Ayora", provincia="Galápagos", irradiacion_h_promedio=5.8),
+                DatosSolaresCiudad(nombre_ciudad="Puerto Baquerizo Moreno", provincia="Galápagos", irradiacion_h_promedio=5.7)
+            ]
+            db.session.add_all(cities)
+        else:
+            print("Las ciudades ya existen.")
+        
+        # Panels
+        if not CatalogoPaneles.query.first():
+            print("Creando paneles...")
+            panels = [
+                CatalogoPaneles(marca="SunPower", modelo="Maxeon 3", eficiencia_r=0.22, area_m2=1.6, precio_unitario=350),
+                CatalogoPaneles(marca="Canadian Solar", modelo="HiKu", eficiencia_r=0.18, area_m2=1.7, precio_unitario=220),
+                CatalogoPaneles(marca="Jinko", modelo="Tiger Pro", eficiencia_r=0.20, area_m2=1.65, precio_unitario=250)
+            ]
+            db.session.add_all(panels)
+        else:
+            print("Los paneles ya existen.")
+            
+        db.session.commit()
+        print("¡Base de datos poblada con éxito!")
+
+if __name__ == '__main__':
+    seed_database()

+ 19 - 0
templates/base.html

@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Calculadora Solar</title>
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
+</head>
+<body>
+    <nav class="navbar navbar-dark bg-primary mb-4">
+        <div class="container">
+            <a class="navbar-brand" href="/">Calculadora de Viabilidad Solar</a>
+        </div>
+    </nav>
+    <div class="container">
+        {% block content %}{% endblock %}
+    </div>
+</body>
+</html>

+ 52 - 0
templates/casas.html

@@ -0,0 +1,52 @@
+{% extends 'base.html' %}
+
+{% block content %}
+<div class="row justify-content-center">
+    <div class="col-md-8">
+        <div class="card mb-4">
+            <div class="card-header bg-primary text-white">Mis Casas</div>
+            <div class="card-body">
+                {% if casas %}
+                <div class="list-group mb-4">
+                    {% for casa in casas %}
+                    <a href="/simular/{{ casa.id_casa }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+                        <div>
+                            <h5 class="mb-1">{{ casa.nombre }}</h5>
+                            <small>{{ casa.ciudad.nombre_ciudad }}, {{ casa.ciudad.provincia }} - {{ casa.area_techo }} m² disponibles</small>
+                        </div>
+                        <span class="badge bg-primary rounded-pill">Simular</span>
+                    </a>
+                    {% endfor %}
+                </div>
+                {% else %}
+                <div class="alert alert-info">No tienes casas registradas. ¡Agrega una para comenzar!</div>
+                {% endif %}
+
+                <hr>
+                <h5>Agregar Nueva Casa</h5>
+                <form action="/casa/nueva" method="POST">
+                    <div class="mb-3">
+                        <label for="nombre" class="form-label">Nombre de la Casa</label>
+                        <input type="text" name="nombre" class="form-control" placeholder="Ej: Casa de Playa" required>
+                    </div>
+                    <div class="row">
+                        <div class="col-md-6 mb-3">
+                            <label for="id_ciudad" class="form-label">Ciudad / Provincia</label>
+                            <select name="id_ciudad" class="form-select" required>
+                                {% for ciudad in ciudades %}
+                                <option value="{{ ciudad.id_ciudad }}">{{ ciudad.nombre_ciudad }} ({{ ciudad.provincia }})</option>
+                                {% endfor %}
+                            </select>
+                        </div>
+                        <div class="col-md-6 mb-3">
+                            <label for="area_techo" class="form-label">Área de Techo Disponible (m²)</label>
+                            <input type="number" step="0.1" name="area_techo" class="form-control" required>
+                        </div>
+                    </div>
+                    <button type="submit" class="btn btn-success">Guardar Casa</button>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock %}

+ 52 - 0
templates/results.html

@@ -0,0 +1,52 @@
+{% extends 'base.html' %}
+
+{% block content %}
+<div class="row justify-content-center">
+    <div class="col-md-8">
+        <div class="card mb-4">
+            <div class="card-header bg-success text-white">Resultados de la Simulación</div>
+            <div class="card-body">
+                <h4 class="card-title text-center mb-4">Generación Estimada: {{ energia }} kWh/mes</h4>
+                
+                <div class="row text-center">
+                    <div class="col-md-4">
+                        <div class="alert alert-info">
+                            <h6>Ahorro Mensual</h6>
+                            <h3>${{ ahorro }}</h3>
+                        </div>
+                    </div>
+                    <div class="col-md-4">
+                        <div class="alert alert-warning">
+                            <h6>Retorno Inversión</h6>
+                            <h3>{{ recuperacion }} Años</h3>
+                        </div>
+                    </div>
+                    <div class="col-md-4">
+                        <div class="alert alert-success">
+                            <h6>CO2 Evitado</h6>
+                            <h3>{{ co2 }} kg</h3>
+                        </div>
+                    </div>
+                </div>
+
+                <hr>
+                <h5>Guardar Proyecto</h5>
+                <form action="/save" method="POST">
+                    <input type="hidden" name="id_casa" value="{{ casa.id_casa }}">
+                    <input type="hidden" name="id_panel" value="{{ panel.id_panel }}">
+                    <input type="hidden" name="cantidad" value="{{ cantidad }}">
+                    <input type="hidden" name="energia" value="{{ energia }}">
+                    <input type="hidden" name="ahorro" value="{{ ahorro }}">
+                    
+                    <div class="mb-3">
+                        <label for="nombre_cliente" class="form-label">Nombre del Cliente / Proyecto</label>
+                        <input type="text" name="nombre_cliente" class="form-control" required value="Proyecto {{ casa.nombre }}">
+                    </div>
+                    <button type="submit" class="btn btn-primary">Guardar Proyecto</button>
+                    <a href="/simular/{{ casa.id_casa }}" class="btn btn-secondary">Volver a Simulación</a>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock %}

+ 65 - 0
templates/simulation.html

@@ -0,0 +1,65 @@
+{% extends 'base.html' %}
+
+{% block content %}
+<div class="row justify-content-center">
+    <div class="col-md-8">
+        <div class="card">
+            <div class="card-header">Simulación para: <strong>{{ casa.nombre }}</strong></div>
+            <div class="card-body">
+                <div class="alert alert-secondary mb-4">
+                    <div class="row">
+                        <div class="col-md-6"><strong>Ubicación:</strong> {{ casa.ciudad.nombre_ciudad }}, {{ casa.ciudad.provincia }}</div>
+                        <div class="col-md-6"><strong>Área Disponible:</strong> {{ casa.area_techo }} m²</div>
+                    </div>
+                </div>
+
+                <form action="/calculate" method="POST">
+                    <input type="hidden" name="id_casa" value="{{ casa.id_casa }}">
+                    
+                    <div class="mb-3">
+                        <label for="id_panel" class="form-label">Modelo de Panel</label>
+                        <select name="id_panel" class="form-select" id="panelSelect" required onchange="updateMaxPanels()">
+                            {% for panel in paneles %}
+                            <option value="{{ panel.id_panel }}" data-area="{{ panel.area_m2 }}">
+                                {{ panel.marca }} {{ panel.modelo }} ({{ panel.eficiencia_r * 100 }}% Eficiencia, {{ panel.area_m2 }} m²)
+                            </option>
+                            {% endfor %}
+                        </select>
+                    </div>
+                    <div class="mb-3">
+                        <label for="cantidad" class="form-label">Cantidad de Paneles</label>
+                        <input type="number" name="cantidad" id="cantidadInput" class="form-control" required min="1">
+                        <div id="maxPanelsHelp" class="form-text text-muted"></div>
+                    </div>
+                    <div class="mb-3">
+                        <label for="precio_kwh" class="form-label">Precio del kWh ($)</label>
+                        <input type="number" step="0.01" name="precio_kwh" class="form-control" value="0.15" required>
+                    </div>
+                    <button type="submit" class="btn btn-success w-100">Calcular</button>
+                    <a href="/" class="btn btn-link w-100 mt-2">Volver a Mis Casas</a>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script>
+    function updateMaxPanels() {
+        const houseArea = {{ casa.area_techo }};
+        const select = document.getElementById('panelSelect');
+        const selectedOption = select.options[select.selectedIndex];
+        const panelArea = parseFloat(selectedOption.getAttribute('data-area'));
+        
+        const maxPanels = Math.floor(houseArea / panelArea);
+        
+        const helpText = document.getElementById('maxPanelsHelp');
+        helpText.innerText = `Máximo recomendado para tu techo: ${maxPanels} paneles`;
+        
+        const input = document.getElementById('cantidadInput');
+        input.max = maxPanels;
+    }
+    
+    // Run on load
+    document.addEventListener('DOMContentLoaded', updateMaxPanels);
+</script>
+{% endblock %}