Browse Source

lets add a crud for houses fuck it

Matthew Trejo 3 days ago
parent
commit
4401f8beff

BIN
__pycache__/models.cpython-313.pyc


BIN
__pycache__/routes.cpython-313.pyc


+ 109 - 0
design.md

@@ -0,0 +1,109 @@
+Proyecto universitario: Calculadora de Viabilidad Solar.
+1. El Modelo Científico (La Fórmula)
+
+Para un proyecto universitario, usaremos la fórmula estándar de estimación de energía fotovoltaica global.
+
+Fórmula Principal:
+E=A⋅r⋅H⋅PR
+
+Donde:
+
+    E (Energía): Es la energía generada (en kWh).
+
+    A (Área): El área total de los paneles solares (en m2).
+
+    r (Rendimiento/Eficiencia): La eficiencia del panel solar (un porcentaje, ej. 18% = 0.18).
+
+    H (Irradiación): La radiación solar media anual o mensual sobre los paneles (en kWh/m2). Este dato varía según la ciudad.
+
+    PR (Performance Ratio): Coeficiente de pérdidas (por calor, cables, polvo, inversor). Generalmente se usa un valor estándar de 0.75 (75%) por defecto.
+
+2. Estructura de la Base de Datos
+
+Necesitarás almacenar tres tipos de información clave. Aquí te propongo un diseño simple de 3 tablas:
+
+Tabla A: Catalogo_Paneles (Datos fijos de productos reales)
+
+    ID_Panel (PK)
+
+    Marca (ej: "SunPower", "Canadian Solar")
+
+    Modelo
+
+    Eficiencia_r (ej: 0.21)
+
+    Area_m2 (ej: 1.6)
+
+    Precio_Unitario (para cálculos económicos)
+
+Tabla B: Datos_Solares_Ciudad (Datos geográficos)
+
+    ID_Ciudad (PK)
+
+    Nombre_Ciudad (ej: "Madrid", "Bogotá", "Ciudad de México")
+
+    Irradiacion_H_Promedio (kWh/m²/día)
+
+        Tip: Puedes buscar tablas en internet con la "Heliofanía" o "Insolación" promedio de ciudades importantes y precargar estos datos.
+
+Tabla C: Proyectos_Usuario (Lo que el usuario guarda)
+
+    ID_Proyecto (PK)
+
+    Nombre_Cliente
+
+    ID_Ciudad (FK)
+
+    ID_Panel (FK)
+
+    Cantidad_Paneles (ej: 10)
+
+    Energia_Estimada_Mensual (Resultado calculado)
+
+    Ahorro_Estimado (Resultado calculado)
+
+    Fecha_Simulacion
+
+3. Flujo del Programa (Algoritmo)
+
+Así debería funcionar tu aplicación paso a paso:
+
+    Entrada de Datos:
+
+        El usuario selecciona una Ciudad de una lista desplegable (el programa obtiene H de la BD).
+
+        El usuario selecciona un modelo de Panel Solar del catálogo (el programa obtiene r y A de la BD).
+
+        El usuario ingresa la cantidad de paneles (o el área disponible en el techo).
+
+    Procesamiento (El Cálculo):
+
+        Calculas el Área Total: Atotal​=Apanel​×cantidad.
+
+        Aplicas la fórmula: E=Atotal​×r×H×30 dıˊas×0.75. (Nota: Multiplicamos por 30 para tener el dato mensual, que es como viene la factura de luz).
+
+    Salida de Resultados:
+
+        Muestras: "Este sistema generará 350 kWh al mes".
+
+        (Opcional) Comparas: "Esto cubre el 80% de un consumo promedio de una casa".
+
+    Almacenamiento:
+
+        Botón "Guardar Proyecto". Se inserta el registro en la tabla Proyectos_Usuario.
+
+4. Ideas para "subir la nota" (Valor Agregado)
+
+Si te sobra tiempo y quieres impresionar al tutor:
+
+    Calculadora de ROI (Retorno de Inversión): Como ya tienes el Precio del Panel en la base de datos y la Energía Generada, puedes pedirle al usuario el "Precio del kWh" que le cobra la compañía eléctrica.
+
+        Algoritmo: Tiempo de recuperacioˊn=(Energıˊa Mensual×Precio kWh)Costo Total de Paneles​.
+
+        Resultado: "Recuperarás tu inversión en 4.5 años".
+
+    Impacto Ambiental: Calcula el CO2​ evitado.
+
+        Factor de emisión promedio ≈0.4 kg CO2​ por kWh generado.
+
+        Muestra un mensaje: "Equivale a plantar 15 árboles".

BIN
instance/solarcalc.db


+ 2 - 0
models.py

@@ -23,6 +23,8 @@ class Casa(db.Model):
     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
+    orientacion = db.Column(db.String(20), nullable=False, default='Norte')
+    inclinacion = db.Column(db.Float, nullable=False, default=0.0)
     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))

+ 62 - 4
routes.py

@@ -11,6 +11,13 @@ def index():
 @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()
+    
+    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')
@@ -23,7 +30,39 @@ def configuracion():
         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')
+    return render_template('configuracion.html', 
+                           precio_kwh=config_precio.valor if config_precio else '0.15', 
+                           ciudades=ciudades,
+                           casas=casas,
+                           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('/casas')
 def casas():
@@ -70,13 +109,16 @@ def proyectos():
 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, id_ciudad=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.casas'))
+    return redirect(url_for('main.configuracion'))
 
 @main.route('/simular/<int:id_casa>')
 def simular(id_casa):
@@ -114,9 +156,25 @@ def calculate():
     
     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
+    energia_mensual = area_total * panel.eficiencia_r * ciudad.irradiacion_h_promedio * days * pr * factor_orientacion
     
     # Bonus: ROI
     ahorro_mensual = energia_mensual * precio_kwh

+ 2 - 2
seed.py

@@ -7,8 +7,8 @@ def seed_database():
         print("Iniciando poblado de base de datos...")
         
         # Opcional: Limpiar tablas existentes si quieres reiniciar de cero
-        # db.drop_all()
-        # db.create_all()
+        db.drop_all()
+        db.create_all()
 
         # Configuración
         if not Configuracion.query.filter_by(clave='precio_kwh').first():

+ 3 - 22
templates/casas.html

@@ -23,28 +23,9 @@
                 {% endif %}
 
                 <hr>
-                <h5>Agregar Nueva Casa</h5>
-                <form action="{{ url_for('main.nueva_casa') }}" 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 class="d-grid gap-2">
+                    <a href="{{ url_for('main.configuracion') }}" class="btn btn-success">Agregar Nueva Casa</a>
+                </div>
             </div>
         </div>
     </div>

+ 91 - 0
templates/configuracion.html

@@ -17,6 +17,97 @@
                 </form>
             </div>
         </div>
+
+        <div class="card mt-4">
+            <div class="card-header {{ 'bg-warning' if casa_editar else 'bg-success' }} text-white">
+                {{ 'Editar Casa' if casa_editar else 'Agregar Nueva Casa' }}
+            </div>
+            <div class="card-body">
+                <form action="{{ url_for('main.editar_casa', id_casa=casa_editar.id_casa) if casa_editar else url_for('main.nueva_casa') }}" 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" value="{{ casa_editar.nombre if casa_editar else '' }}" 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 }}" {{ 'selected' if casa_editar and casa_editar.id_ciudad == ciudad.id_ciudad else '' }}>
+                                    {{ 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" value="{{ casa_editar.area_techo if casa_editar else '' }}" required>
+                        </div>
+                    </div>
+                    <div class="row">
+                        <div class="col-md-6 mb-3">
+                            <label for="orientacion" class="form-label">Orientación del Techo</label>
+                            <select class="form-select" id="orientacion" name="orientacion" required>
+                                {% set orientaciones = ['Norte', 'Sur', 'Este', 'Oeste', 'Noreste', 'Noroeste', 'Sureste', 'Suroeste', 'Plano'] %}
+                                {% for o in orientaciones %}
+                                <option value="{{ o }}" {{ 'selected' if casa_editar and casa_editar.orientacion == o else '' }}>{{ o }}</option>
+                                {% endfor %}
+                            </select>
+                        </div>
+                        <div class="col-md-6 mb-3">
+                            <label for="inclinacion" class="form-label">Inclinación del Techo (Grados)</label>
+                            <input type="number" step="1" class="form-control" id="inclinacion" name="inclinacion" value="{{ casa_editar.inclinacion if casa_editar else '0' }}" required>
+                            <div class="form-text">0° es plano. Típico es 15-30°.</div>
+                        </div>
+                    </div>
+                    <button type="submit" class="btn {{ 'btn-warning' if casa_editar else 'btn-success' }}">
+                        {{ 'Actualizar Casa' if casa_editar else 'Guardar Casa' }}
+                    </button>
+                    {% if casa_editar %}
+                    <a href="{{ url_for('main.configuracion') }}" class="btn btn-secondary">Cancelar</a>
+                    {% endif %}
+                </form>
+            </div>
+        </div>
+
+        {% if casas %}
+        <div class="card mt-4 mb-4">
+            <div class="card-header bg-info text-white">Casas Registradas</div>
+            <div class="card-body">
+                <div class="table-responsive">
+                    <table class="table table-hover">
+                        <thead>
+                            <tr>
+                                <th>Nombre</th>
+                                <th>Ciudad</th>
+                                <th>Área (m²)</th>
+                                <th>Orientación</th>
+                                <th>Acciones</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {% for casa in casas %}
+                            <tr>
+                                <td>{{ casa.nombre }}</td>
+                                <td>{{ casa.ciudad.nombre_ciudad }}</td>
+                                <td>{{ casa.area_techo }}</td>
+                                <td>{{ casa.orientacion }}</td>
+                                <td>
+                                    <div class="btn-group" role="group">
+                                        <a href="{{ url_for('main.configuracion', edit_id=casa.id_casa) }}" class="btn btn-sm btn-warning">Editar</a>
+                                        <form action="{{ url_for('main.eliminar_casa', id_casa=casa.id_casa) }}" method="POST" onsubmit="return confirm('¿Estás seguro de eliminar esta casa?');" style="display: inline;">
+                                            <button type="submit" class="btn btn-sm btn-danger">Eliminar</button>
+                                        </form>
+                                    </div>
+                                </td>
+                            </tr>
+                            {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+        {% endif %}
     </div>
 </div>
 {% endblock %}

+ 123 - 0
templates/proyectos.html

@@ -50,6 +50,7 @@
                         <th>Cantidad</th>
                         <th>Energía (kWh/mes)</th>
                         <th>Ahorro ($/mes)</th>
+                        <th>Acciones</th>
                     </tr>
                 </thead>
                 <tbody>
@@ -63,6 +64,24 @@
                         <td>{{ proyecto.cantidad_paneles }}</td>
                         <td>{{ "%.2f"|format(proyecto.energia_estimada_mensual) }}</td>
                         <td>{{ "%.2f"|format(proyecto.ahorro_estimado) if proyecto.ahorro_estimado else '-' }}</td>
+                        <td>
+                            <button type="button" class="btn btn-info btn-sm text-white" data-bs-toggle="modal" data-bs-target="#detallesModal"
+                                data-cliente="{{ proyecto.nombre_cliente }}"
+                                data-fecha="{{ proyecto.fecha_referencia.strftime('%Y-%m-%d') }}"
+                                data-casa="{{ proyecto.casa.nombre if proyecto.casa else 'N/A' }}"
+                                data-ciudad="{{ proyecto.ciudad.nombre_ciudad }}"
+                                data-provincia="{{ proyecto.ciudad.provincia }}"
+                                data-panel="{{ proyecto.panel.marca }} {{ proyecto.panel.modelo }}"
+                                data-eficiencia="{{ '%.1f'|format(proyecto.panel.eficiencia_r * 100) }}"
+                                data-cantidad="{{ proyecto.cantidad_paneles }}"
+                                data-energia="{{ '%.2f'|format(proyecto.energia_estimada_mensual) }}"
+                                data-ahorro="{{ '%.2f'|format(proyecto.ahorro_estimado) if proyecto.ahorro_estimado else '0' }}"
+                                data-consumo="{{ proyecto.consumo_actual_kwh if proyecto.consumo_actual_kwh else '0' }}"
+                                data-costo="{{ proyecto.costo_actual_mensual if proyecto.costo_actual_mensual else '0' }}"
+                            >
+                                Ver Detalles
+                            </button>
+                        </td>
                     </tr>
                     {% endfor %}
                 </tbody>
@@ -75,11 +94,115 @@
         {% endif %}
     </div>
 </div>
+
+<!-- Modal Detalles -->
+<div class="modal fade" id="detallesModal" tabindex="-1" aria-labelledby="detallesModalLabel" aria-hidden="true">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header bg-primary text-white">
+                <h5 class="modal-title" id="detallesModalLabel">Detalles del Proyecto</h5>
+                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
+            </div>
+            <div class="modal-body">
+                <div class="row mb-4">
+                    <div class="col-md-6">
+                        <h6 class="text-muted">Información General</h6>
+                        <p><strong>Cliente:</strong> <span id="modalCliente"></span></p>
+                        <p><strong>Fecha Simulación:</strong> <span id="modalFecha"></span></p>
+                        <p><strong>Casa:</strong> <span id="modalCasa"></span></p>
+                        <p><strong>Ubicación:</strong> <span id="modalUbicacion"></span></p>
+                    </div>
+                    <div class="col-md-6">
+                        <h6 class="text-muted">Sistema Solar</h6>
+                        <p><strong>Panel:</strong> <span id="modalPanel"></span></p>
+                        <p><strong>Eficiencia:</strong> <span id="modalEficiencia"></span>%</p>
+                        <p><strong>Cantidad:</strong> <span id="modalCantidad"></span> paneles</p>
+                    </div>
+                </div>
+                
+                <hr>
+                
+                <h6 class="text-muted mb-3">Análisis Financiero y Energético</h6>
+                <div class="row text-center">
+                    <div class="col-md-3">
+                        <div class="p-2 border rounded bg-light">
+                            <small class="d-block text-muted">Consumo Actual</small>
+                            <strong class="h5"><span id="modalConsumo"></span> kWh</strong>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="p-2 border rounded bg-light">
+                            <small class="d-block text-muted">Factura Actual</small>
+                            <strong class="h5 text-danger">$<span id="modalCosto"></span></strong>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="p-2 border rounded bg-light">
+                            <small class="d-block text-muted">Generación Solar</small>
+                            <strong class="h5 text-success"><span id="modalEnergia"></span> kWh</strong>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="p-2 border rounded bg-light">
+                            <small class="d-block text-muted">Ahorro Estimado</small>
+                            <strong class="h5 text-primary">$<span id="modalAhorro"></span></strong>
+                        </div>
+                    </div>
+                </div>
+                
+                <div class="alert alert-info mt-4 text-center">
+                    <strong>Nuevo Costo Estimado:</strong> $<span id="modalNuevoCosto"></span> / mes
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
+            </div>
+        </div>
+    </div>
+</div>
+
 {% endblock %}
 
 {% block scripts %}
 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 <script>
+    // Modal Logic
+    const detallesModal = document.getElementById('detallesModal');
+    detallesModal.addEventListener('show.bs.modal', event => {
+        const button = event.relatedTarget;
+        
+        // Extract info from data-* attributes
+        const cliente = button.getAttribute('data-cliente');
+        const fecha = button.getAttribute('data-fecha');
+        const casa = button.getAttribute('data-casa');
+        const ciudad = button.getAttribute('data-ciudad');
+        const provincia = button.getAttribute('data-provincia');
+        const panel = button.getAttribute('data-panel');
+        const eficiencia = button.getAttribute('data-eficiencia');
+        const cantidad = button.getAttribute('data-cantidad');
+        const energia = button.getAttribute('data-energia');
+        const ahorro = parseFloat(button.getAttribute('data-ahorro'));
+        const consumo = button.getAttribute('data-consumo');
+        const costo = parseFloat(button.getAttribute('data-costo'));
+        
+        // Calculate new cost
+        const nuevoCosto = Math.max(0, costo - ahorro).toFixed(2);
+
+        // Update the modal's content.
+        detallesModal.querySelector('#modalCliente').textContent = cliente;
+        detallesModal.querySelector('#modalFecha').textContent = fecha;
+        detallesModal.querySelector('#modalCasa').textContent = casa;
+        detallesModal.querySelector('#modalUbicacion').textContent = `${ciudad}, ${provincia}`;
+        detallesModal.querySelector('#modalPanel').textContent = panel;
+        detallesModal.querySelector('#modalEficiencia').textContent = eficiencia;
+        detallesModal.querySelector('#modalCantidad').textContent = cantidad;
+        detallesModal.querySelector('#modalEnergia').textContent = energia;
+        detallesModal.querySelector('#modalAhorro').textContent = ahorro.toFixed(2);
+        detallesModal.querySelector('#modalConsumo').textContent = consumo;
+        detallesModal.querySelector('#modalCosto').textContent = costo.toFixed(2);
+        detallesModal.querySelector('#modalNuevoCosto').textContent = nuevoCosto;
+    });
+
     const ctx = document.getElementById('savingsChart').getContext('2d');
     const savingsChart = new Chart(ctx, {
         type: 'bar',

+ 2 - 0
templates/simulation.html

@@ -10,6 +10,8 @@
                     <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 class="col-md-6"><strong>Orientación:</strong> {{ casa.orientacion }}</div>
+                        <div class="col-md-6"><strong>Inclinación:</strong> {{ casa.inclinacion }}°</div>
                     </div>
                 </div>