Jelajahi Sumber

add graphs for price comparison

Matthew Trejo 4 hari lalu
induk
melakukan
23da881c34
9 mengubah file dengan 176 tambahan dan 16 penghapusan
  1. TEMPAT SAMPAH
      __pycache__/models.cpython-313.pyc
  2. TEMPAT SAMPAH
      __pycache__/routes.cpython-313.pyc
  3. TEMPAT SAMPAH
      instance/solarcalc.db
  4. 2 1
      models.py
  5. 46 5
      routes.py
  6. 1 0
      templates/base.html
  7. 12 3
      templates/index.html
  8. 94 1
      templates/proyectos.html
  9. 21 6
      templates/results.html

TEMPAT SAMPAH
__pycache__/models.cpython-313.pyc


TEMPAT SAMPAH
__pycache__/routes.cpython-313.pyc


TEMPAT SAMPAH
instance/solarcalc.db


+ 2 - 1
models.py

@@ -40,7 +40,8 @@ class ProyectosUsuario(db.Model):
     ahorro_estimado = db.Column(db.Float, nullable=True)
     consumo_actual_kwh = db.Column(db.Float, nullable=True)
     costo_actual_mensual = db.Column(db.Float, nullable=True)
-    fecha_simulacion = db.Column(db.DateTime, default=db.func.current_timestamp())
+    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
+    fecha_referencia = db.Column(db.DateTime, nullable=False, 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))

+ 46 - 5
routes.py

@@ -1,5 +1,6 @@
 from flask import Blueprint, render_template, request, redirect, url_for, flash
 from models import db, CatalogoPaneles, DatosSolaresCiudad, ProyectosUsuario, Casa, Configuracion
+from datetime import datetime
 
 main = Blueprint('main', __name__)
 
@@ -33,8 +34,37 @@ def casas():
 
 @main.route('/proyectos')
 def proyectos():
-    proyectos = ProyectosUsuario.query.order_by(ProyectosUsuario.fecha_simulacion.desc()).all()
-    return render_template('proyectos.html', proyectos=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():
@@ -101,7 +131,7 @@ def calculate():
     
     # 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
@@ -119,7 +149,8 @@ def calculate():
                            consumo_kwh=consumo_kwh,
                            costo_mensual=round(costo_mensual, 2),
                            nuevo_costo_mensual=round(nuevo_costo_mensual, 2),
-                           porcentaje_cobertura=round(porcentaje_cobertura, 1))
+                           porcentaje_cobertura=round(porcentaje_cobertura, 1),
+                           today=datetime.now().strftime('%Y-%m-%d'))
 
 @main.route('/save', methods=['POST'])
 def save():
@@ -132,9 +163,18 @@ def save():
     
     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,
@@ -144,7 +184,8 @@ def save():
         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
+        costo_actual_mensual=float(costo_mensual) if costo_mensual and float(costo_mensual) > 0 else None,
+        fecha_referencia=fecha_referencia
     )
     
     db.session.add(proyecto)

+ 1 - 0
templates/base.html

@@ -55,5 +55,6 @@
             localStorage.setItem('theme', newTheme);
         });
     </script>
+    {% block scripts %}{% endblock %}
 </body>
 </html>

+ 12 - 3
templates/index.html

@@ -2,10 +2,10 @@
 
 {% block content %}
 <div class="row justify-content-center mt-5">
-    <div class="col-md-8 text-center">
+    <div class="col-md-10 text-center">
         <h1 class="mb-5">Bienvenido a la Calculadora Solar</h1>
         <div class="row">
-            <div class="col-md-6 mb-4">
+            <div class="col-md-4 mb-4">
                 <div class="card h-100">
                     <div class="card-body d-flex flex-column justify-content-center">
                         <h5 class="card-title">Nueva Simulación</h5>
@@ -14,7 +14,7 @@
                     </div>
                 </div>
             </div>
-            <div class="col-md-6 mb-4">
+            <div class="col-md-4 mb-4">
                 <div class="card h-100">
                     <div class="card-body d-flex flex-column justify-content-center">
                         <h5 class="card-title">Simulaciones Guardadas</h5>
@@ -23,6 +23,15 @@
                     </div>
                 </div>
             </div>
+            <div class="col-md-4 mb-4">
+                <div class="card h-100">
+                    <div class="card-body d-flex flex-column justify-content-center">
+                        <h5 class="card-title">Configuración</h5>
+                        <p class="card-text">Ajusta precios y parámetros del sistema.</p>
+                        <a href="{{ url_for('main.configuracion') }}" class="btn btn-secondary mt-3">Configuración</a>
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
 </div>

+ 94 - 1
templates/proyectos.html

@@ -8,7 +8,36 @@
             <a href="{{ url_for('main.index') }}" class="btn btn-secondary">Volver al Inicio</a>
         </div>
         
+        <!-- Filter Form -->
+        <div class="card mb-4">
+            <div class="card-body">
+                <form action="{{ url_for('main.proyectos') }}" method="GET" class="row g-3 align-items-end">
+                    <div class="col-md-4">
+                        <label for="id_casa" class="form-label">Filtrar por Casa</label>
+                        <select name="id_casa" class="form-select" onchange="this.form.submit()">
+                            <option value="">Todas las casas</option>
+                            {% for casa in casas %}
+                            <option value="{{ casa.id_casa }}" {% if selected_casa == casa.id_casa %}selected{% endif %}>{{ casa.nombre }}</option>
+                            {% endfor %}
+                        </select>
+                    </div>
+                    <div class="col-md-2">
+                        <a href="{{ url_for('main.proyectos') }}" class="btn btn-outline-secondary">Limpiar Filtro</a>
+                    </div>
+                </form>
+            </div>
+        </div>
+
         {% if proyectos %}
+        <div class="card mb-4">
+            <div class="card-header">
+                Comparativa de Costos Mensuales: Sin Solar vs Con Solar
+            </div>
+            <div class="card-body">
+                <canvas id="savingsChart" width="400" height="100"></canvas>
+            </div>
+        </div>
+
         <div class="table-responsive">
             <table class="table table-striped table-hover">
                 <thead>
@@ -26,7 +55,7 @@
                 <tbody>
                     {% for proyecto in proyectos %}
                     <tr>
-                        <td>{{ proyecto.fecha_simulacion.strftime('%Y-%m-%d %H:%M') }}</td>
+                        <td>{{ proyecto.fecha_referencia.strftime('%Y-%m-%d') }}</td>
                         <td>{{ proyecto.nombre_cliente }}</td>
                         <td>{{ proyecto.ciudad.nombre_ciudad }}</td>
                         <td>{{ proyecto.casa.nombre if proyecto.casa else 'N/A' }}</td>
@@ -47,3 +76,67 @@
     </div>
 </div>
 {% endblock %}
+
+{% block scripts %}
+<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+<script>
+    const ctx = document.getElementById('savingsChart').getContext('2d');
+    const savingsChart = new Chart(ctx, {
+        type: 'bar',
+        data: {
+            labels: {{ labels | tojson }},
+            datasets: [
+                {
+                    label: 'Costo Sin Solar ($)',
+                    data: {{ costo_sin_solar | tojson }},
+                    backgroundColor: 'rgba(220, 53, 69, 0.6)',
+                    borderColor: 'rgba(220, 53, 69, 1)',
+                    borderWidth: 1
+                },
+                {
+                    label: 'Costo Con Solar ($)',
+                    data: {{ costo_con_solar | tojson }},
+                    backgroundColor: 'rgba(25, 135, 84, 0.6)',
+                    borderColor: 'rgba(25, 135, 84, 1)',
+                    borderWidth: 1
+                }
+            ]
+        },
+        options: {
+            scales: {
+                y: {
+                    beginAtZero: true,
+                    title: {
+                        display: true,
+                        text: 'Costo Mensual ($)'
+                    }
+                }
+            },
+            plugins: {
+                tooltip: {
+                    callbacks: {
+                        footer: function(tooltipItems) {
+                            let sinSolar = 0;
+                            let conSolar = 0;
+                            tooltipItems.forEach(function(tooltipItem) {
+                                if (tooltipItem.datasetIndex === 0) {
+                                    sinSolar = tooltipItem.raw;
+                                } else if (tooltipItem.datasetIndex === 1) {
+                                    conSolar = tooltipItem.raw;
+                                }
+                            });
+                            // Nota: Como el tooltip muestra un item a la vez por defecto en modo 'nearest', 
+                            // esto funciona mejor si el modo es 'index'.
+                            return '';
+                        }
+                    }
+                }
+            },
+            interaction: {
+                mode: 'index',
+                intersect: false,
+            }
+        }
+    });
+</script>
+{% endblock %}

+ 21 - 6
templates/results.html

@@ -29,13 +29,24 @@
                                     </div>
                                 </div>
                                 <div class="progress mt-3" style="height: 25px;">
-                                    <div class="progress-bar bg-success" role="progressbar" style="width: {{ porcentaje_cobertura }}%;" aria-valuenow="{{ porcentaje_cobertura }}" aria-valuemin="0" aria-valuemax="100">
-                                        Solar {{ porcentaje_cobertura }}%
-                                    </div>
-                                    <div class="progress-bar bg-secondary" role="progressbar" style="width: {{ 100 - porcentaje_cobertura }}%;" aria-valuenow="{{ 100 - porcentaje_cobertura }}" aria-valuemin="0" aria-valuemax="100">
-                                        Red {{ "%.1f"|format(100 - porcentaje_cobertura) }}%
-                                    </div>
+                                    {% if porcentaje_cobertura >= 100 %}
+                                        <div class="progress-bar bg-success" role="progressbar" style="width: 100%;" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
+                                            Solar {{ porcentaje_cobertura }}% (Excedente)
+                                        </div>
+                                    {% else %}
+                                        <div class="progress-bar bg-success" role="progressbar" style="width: {{ porcentaje_cobertura }}%;" aria-valuenow="{{ porcentaje_cobertura }}" aria-valuemin="0" aria-valuemax="100">
+                                            Solar {{ porcentaje_cobertura }}%
+                                        </div>
+                                        <div class="progress-bar bg-secondary" role="progressbar" style="width: {{ 100 - porcentaje_cobertura }}%;" aria-valuenow="{{ 100 - porcentaje_cobertura }}" aria-valuemin="0" aria-valuemax="100">
+                                            Red {{ "%.1f"|format(100 - porcentaje_cobertura) }}%
+                                        </div>
+                                    {% endif %}
+                                </div>
+                                {% if porcentaje_cobertura > 100 %}
+                                <div class="alert alert-info mt-3 text-center">
+                                    <strong>¡Excelente!</strong> Tu sistema genera un excedente de {{ "%.1f"|format(energia - consumo_kwh) }} kWh.
                                 </div>
+                                {% endif %}
                             </div>
                         </div>
                     </div>
@@ -78,6 +89,10 @@
                         <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>
+                    <div class="mb-3">
+                        <label for="fecha_referencia" class="form-label">Fecha de Simulación / Factura</label>
+                        <input type="date" name="fecha_referencia" class="form-control" required value="{{ today }}">
+                    </div>
                     <button type="submit" class="btn btn-primary">Guardar Proyecto</button>
                     <a href="{{ url_for('main.simular', id_casa=casa.id_casa) }}" class="btn btn-secondary">Volver a Simulación</a>
                 </form>