Browse Source

Merge pull request 'repostajes' (#1) from repostajes into main

Reviewed-on: https://gitserver.reymota.es/creylopez/misfinanzas/pulls/1
creylopez 8 months ago
parent
commit
bb0ac206e6

+ 46 - 45
Jenkinsfile

@@ -23,7 +23,8 @@ pipeline {
     booleanParam(name: 'borraclaims', description: 'Borra PVC', defaultValue: false)
     booleanParam(name: 'namespace', description: 'Crea namespace', defaultValue: false)
     booleanParam(name: 'borranamespace', description: 'Borra namespace', defaultValue: false)
-    booleanParam(name: 'helm', description: 'Instala helm', defaultValue: false)
+    booleanParam(name: 'creadb', description: 'Crea el objeto para crear la DB en el cúster', defaultValue: false)
+    booleanParam(name: 'borradb', description: 'Borra el objeto que creó la DB, no la DB', defaultValue: false)
   }
   
   stages {
@@ -35,12 +36,9 @@ pipeline {
         echo "Creando las PVC..." 
         container('kubectl') {
           withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
-          sh """#!/bin/bash
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/pvc-${NOMBRE_APP}.yaml
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/pvc-static.yaml
-
-            kubectl create -f K8S/pvc-${NOMBRE_APP}.yaml
-            kubectl create -f K8S/pvc-static.yaml
+	  sh """#!/bin/bash
+	    cat K8S/pvc-${NOMBRE_APP}.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
+	    cat K8S/pvc-static.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
             """
           }
         }
@@ -55,20 +53,47 @@ pipeline {
         echo "Borrando las PVC..." 
         container('kubectl') {
           withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
-          sh """#!/bin/bash
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/pvc-${NOMBRE_APP}.yaml
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/pvc-static.yaml
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/patchPVC.sh
-
-            sh K8S/patchPVC.sh||true
-            kubectl delete -f K8S/pvc-${NOMBRE_APP}.yaml
-            kubectl delete -f K8S/pvc-static.yaml
+	  sh """#!/bin/bash
+	    cat K8S/pvc-${NOMBRE_APP}.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+	    cat K8S/pvc-static.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
             """
           }
         }
       }     
     }
 
+    stage('Crea el objeto para crear la DB') {
+      when {
+        expression { params.creadb}
+      }
+      steps {
+        echo "Creando el objeto que crea la DB..." 
+        container('kubectl') {
+          withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
+	  sh """#!/bin/bash
+	    kubectl create -f K8S/${NOMBRE_APP}-db.yaml 
+            """
+          }
+        }
+      }      
+    }
+
+    stage('Borra el objeto que creó la DB, no la DB') {
+      when {
+        expression { params.borradb}
+      }
+      steps {
+        echo "Borrando el objeto que creó la DB..." 
+        container('kubectl') {
+          withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
+	  sh """#!/bin/bash
+	    kubectl delete -f K8S/${NOMBRE_APP}-db.yaml 
+            """
+          }
+        }
+      }      
+    }
+
     stage('Borra todo') {
       when {
         expression { params.clean }
@@ -104,7 +129,9 @@ pipeline {
         echo "Borrando namespace..." 
         container('kubectl') {
           withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
-            sh 'kubectl delete -f K8S/namespace.yaml||true'
+	  sh """#!/bin/bash
+	    cat K8S/namespace.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl delete -f -
+            """
           }
         }
       }      
@@ -118,9 +145,8 @@ pipeline {
         echo "Creando namespace..." 
         container('kubectl') {
           withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
-          sh """#!/bin/bash
-            sed -i 's|<NS>|"${NAMESPACE}"|' K8S/namespace.yaml
-            kubectl create -f K8S/namespace.yaml
+	  sh """#!/bin/bash
+	    cat K8S/namespace.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl create -f -
             """
           }
         }
@@ -157,7 +183,7 @@ pipeline {
       steps {
         echo "Creando todo..." 
         container('kubectl') {
-          withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {           
+          withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
           sh """#!/bin/bash
             sed -i 's|<NS>|"${NAMESPACE}"|' K8S/${NOMBRE_APP}-deployment.yaml
             sed -i 's|<NS>|"${NAMESPACE}"|' K8S/${NOMBRE_APP}-ingress.yaml
@@ -186,7 +212,6 @@ pipeline {
           expression { !params.borranamespace } 
           expression { !params.claims }
           expression { !params.borraclaims }
-          expression { !params.install }
         }
       }
       steps {
@@ -203,30 +228,6 @@ pipeline {
         }
       }
     }   
-
-    stage('Instala Helm Chart') {
-      when {
-        allOf {
-          expression { !params.clean }
-          expression { !params.namespace }
-          expression { !params.borranamespace } 
-          expression { !params.claims }
-          expression { !params.borraclaims }
-          expression { !params.install }
-          expression { params.helm }
-        }
-      }
-      steps {
-        echo "Instalando el helm chart" 
-        container('kubectl') {
-          withCredentials([file(credentialsId: env.Destino, variable: 'KUBECONFIG')]) {
-          sh """#!/bin/bash
-            helm upgrade finanzas finanzas --namespace finanzas --set=image.tag=${BUILD_NUMBER}
-            """
-          }
-        }
-      }
-    }
   }
   
   post {

+ 1 - 1
K8S/Makefile

@@ -1,6 +1,6 @@
 export REGISTRY=registry.reymota.es
 export IMG_VERSION = alpine-17
-export NAMESPACE = fdev
+export NAMESPACE = finanzas
 
 # limpia todo
 all: imagen clean install

+ 10 - 0
K8S/finanzas-db.yaml

@@ -0,0 +1,10 @@
+apiVersion: postgresql.cnpg.io/v1
+kind: Database
+metadata:
+  name: finanzas-db
+  namespace: reymota-pg-cluster
+spec:
+  name: finanzas
+  owner: creylopez
+  cluster:
+    name: postgres-cluster

+ 1 - 1
K8S/finanzas-deployment.yaml

@@ -84,7 +84,7 @@ spec:
               key: SQL_PORT
               name: env-prod
         envFrom:
-          - secretRef:
+        - secretRef:
             name: postgres-secret
         ports:
         - containerPort: 8000

+ 0 - 63
SCHEMAS.md

@@ -1,63 +0,0 @@
-## Implementación SIMPLE (sin librerías externas)
-
-### Crear schemas
-    CREATE SCHEMA proyecto_a;
-    CREATE SCHEMA proyecto_b;
-
-### Configurar search_path por proyecto
-
-En cada proyecto Django, su ```settings.py```:
-
-    DATABASES = {
-        "default": {
-            "ENGINE": "django.db.backends.postgresql",
-            "NAME": "miservicio_db",
-            "USER": "dbuser",
-            "PASSWORD": "secret",
-            "HOST": "localhost",
-            "PORT": 5432,
-            "OPTIONS": {
-                "options": "-c search_path=proyecto_a,public"
-            }
-        }
-    }
-
-En el otro proyecto:
-    "options": "-c search_path=proyecto_b,public"
-
-### Migrar normalmente
-    python manage.py migrate
-
-Django creará automáticamente:
-    
-    proyecto_a.django_migrations
-    proyecto_a.auth_user
-    proyecto_a.miapp_modelo
-    
-- Sin prefijos
-- Sin routers
-- Sin hacks
-- 100 % soportado
-
-### Este detalle es CLAVE
-
-Cada proyecto tendrá sus propias:
-
-- django_migrations
-- auth_*
-- contenttypes
-- sessions
-- admin
-
-NO compartas estas tablas entre proyectos.
-Eso es pedir corrupción de datos.
-
-## Comandos para gestionar schemas
-
-To display schemas:
-
-    \dn - list of schemas
-
-To change schema, you can try:
-
-    SET search_path TO

+ 0 - 17
TODO.md

@@ -1,17 +0,0 @@
-# Cosas pendientes
-
-## 2025 05 29
-
-### Informes en PDF
-Ya saca informes en PDF, pero
-1. el saldo es solo del periodo elegido. No tiene en cuenta el saldo inicial en ese periodo, habría que calcularlo.
-2. las fechas del informe están en formato YYYY-MM-DD
-3. hay que quitar el icono de ver informe normal y probablemente borrar funciones que ya no se usen
-
-## 2025 07 03
-
-Poner opción en menú lateral para búsqueda global -> OK
-
-En el informe de cuentas, poner opción de duplicar
-
-Quitar filtro en views de listar_cuentas

+ 6 - 5
src/misfinanzas/settings.py

@@ -51,6 +51,7 @@ INSTALLED_APPS = [
 
     'finanzas',
     'reymotausers',
+    'repostajes',
 
     'rest_framework',
 ]
@@ -96,14 +97,14 @@ WSGI_APPLICATION = 'misfinanzas.wsgi.application'
 DATABASES = {
     'default': {
         "ENGINE": "django.db.backends.postgresql",
-        "NAME": "reymotadb",
+        "NAME": "finanzas",
         "USER": "creylopez",
         "PASSWORD": "Dsa-0213",
-        "HOST": "postgres-cluster-rw.reymota-pg-cluster.svc.cluster.local",
+        "HOST": os.environ.get('SQL_HOST','postgres-cluster-rw.reymota-pg-cluster.svc.cluster.local'),
         "PORT": "5432",
-        "OPTIONS": {
-            "options": "-c search_path=finanzas,public"
-        }
+        #"OPTIONS": {
+            #"options": "-c search_path=finanzas,public"
+        #}
     }
 }
 

+ 0 - 1
src/misfinanzas/templatetags/filtros_de_entorno.py

@@ -3,7 +3,6 @@ from django import template
 
 register = template.Library()
 
-
 @register.filter
 def muestra_version(clave):
     return os.getenv(clave, '')

+ 1 - 0
src/misfinanzas/urls.py

@@ -30,6 +30,7 @@ urlpatterns = [
     path('', include('finanzas.urls')),
 
     path('usuarios/', include("reymotausers.urls")),
+    path('repostajes/', include('repostajes.urls')),
 
     path("acerca-de/", views.acerca_de, name="acerca_de"),
     path('ver-fichero-log/<str:fichero>/', views.ver_fichero_log, name='ver_fichero_log'),

+ 1 - 0
src/repostajes/.gitignore

@@ -0,0 +1 @@
+migrations/

+ 0 - 0
src/repostajes/__init__.py


+ 9 - 0
src/repostajes/admin.py

@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+# Register your models here.
+
+
+from .models import Vehiculo, Repostaje
+
+admin.site.register(Vehiculo)
+admin.site.register(Repostaje)

+ 6 - 0
src/repostajes/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class RepostajesConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'repostajes'

+ 41 - 0
src/repostajes/forms.py

@@ -0,0 +1,41 @@
+from django import forms
+
+from .models import Vehiculo, Repostaje
+
+
+class VehiculoForm(forms.ModelForm):
+    class Meta:
+        model = Vehiculo
+        fields = ['marca', 'modelo', 'matricula', 'foto']
+
+    marca = forms.CharField(
+        widget=forms.TextInput(attrs={'class': 'form-control'}))
+    modelo = forms.CharField(
+        widget=forms.TextInput(attrs={'class': 'form-control'}))
+    matricula = forms.CharField(
+        widget=forms.TextInput(attrs={'class': 'form-control'}))
+
+
+class RepostajeForm(forms.ModelForm):
+    class Meta:
+        model = Repostaje
+        fields = ['fecha', 'vehiculo', 'kms', 'litros', 'importe']
+        exclude = ['descuento', 'precioxlitro']
+
+    fecha = forms.DateField(
+            widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}))
+
+    vehiculo = forms.ModelChoiceField(
+        queryset=Vehiculo.objects.all(),
+        widget=forms.Select(attrs={'class': 'form-control'}))
+
+    kms = forms.DecimalField(
+        widget=forms.TextInput(attrs={'class': 'form-control'}))
+
+    litros = forms.DecimalField(
+        widget=forms.NumberInput(attrs={'class': 'form-control'}))
+
+    importe = forms.DecimalField(
+        widget=forms.NumberInput(attrs={'class': 'form-control'}))
+
+    aplica_descuento = forms.BooleanField(initial=False, required=False)

+ 0 - 0
src/repostajes/management/__init__.py


+ 0 - 0
src/repostajes/management/commands/__init__.py


+ 55 - 0
src/repostajes/management/commands/importar_repostajes.py

@@ -0,0 +1,55 @@
+import json
+from django.core.management.base import BaseCommand
+from repostajes.models import Repostaje, Vehiculo
+
+
+class Command(BaseCommand):
+    help = "Importa repostajes nuevos desde un archivo JSON"
+
+    def add_arguments(self, parser):
+        parser.add_argument('archivo_json', type=str, help="Ruta del archivo JSON")
+
+    def handle(self, *args, **kwargs):
+        archivo_json = kwargs['archivo_json']
+
+        try:
+            with open(archivo_json, 'r', encoding='utf-8') as file:
+                datos = json.load(file)
+
+            self.stdout.write(self.style.WARNING(f"\nSe encontraron {len(datos)} repostajes en el archivo '{archivo_json}'."))
+            confirmar = input("¿Deseas continuar con la importación? (s/n): ").strip().lower()
+
+            if confirmar != 's':
+                self.stdout.write(self.style.ERROR("Importación cancelada."))
+                return
+
+            repostajes_creados = 0
+            for repostaje_data in datos:
+                try:
+                    vehiculo = Vehiculo.objects.get(matricula=repostaje_data["vehiculo_matricula"])
+
+                    repostaje, creado = Repostaje.objects.get_or_create(
+                        vehiculo=vehiculo,
+                        fecha=repostaje_data['fecha'],
+                        kms=repostaje_data['kms'],
+                        defaults={
+                            'litros': repostaje_data['litros'],
+                            'descuento': repostaje_data['descuento'],
+                            'importe': repostaje_data['importe'],
+                            'precioxlitro': repostaje_data['precioxlitro'],
+                            'kmsrecorridos': repostaje_data['kmsrecorridos'],
+                            'consumo': repostaje_data['consumo']
+                        }
+                    )
+                    if creado:
+                        repostajes_creados += 1
+
+                except Vehiculo.DoesNotExist:
+                    self.stderr.write(self.style.ERROR(f"Vehiculo con matrícula '{repostaje_data['vehiculo_matricula']}' no encontrado."))
+
+            self.stdout.write(self.style.SUCCESS(f'Se importaron {repostajes_creados} repostajes correctamente.'))
+
+        except FileNotFoundError:
+            self.stderr.write(self.style.ERROR(f"El archivo {archivo_json} no se encontró."))
+        except json.JSONDecodeError:
+            self.stderr.write(self.style.ERROR("Error al leer el archivo JSON. Asegúrate de que el formato sea correcto."))

+ 48 - 0
src/repostajes/management/commands/importar_vehiculos.py

@@ -0,0 +1,48 @@
+import json
+from django.core.management.base import BaseCommand
+from repostajes.models import Vehiculo
+
+
+class Command(BaseCommand):
+    help = "Importa vehiculos desde un archivo JSON"
+
+    def add_arguments(self, parser):
+        parser.add_argument('archivo_json', type=str, help="Ruta del archivo JSON")
+
+    def handle(self, *args, **kwargs):
+        archivo_json = kwargs['archivo_json']
+
+        try:
+            with open(archivo_json, 'r', encoding='utf-8') as file:
+                datos = json.load(file)
+
+            self.stdout.write(self.style.WARNING(f"\nSe encontraron {len(datos)} vehiculos en el archivo '{archivo_json}'."))
+            confirmar = input("¿Deseas continuar con la importación? (s/n): ").strip().lower()
+
+            if confirmar != 's':
+                self.stdout.write(self.style.ERROR("Importación cancelada."))
+                return
+
+            vehiculos_creados = 0
+            for vehiculo_data in datos:
+                try:
+                    vehiculo, creado = Vehiculo.objects.get_or_create(
+                        matricula=vehiculo_data["matricula"],
+                        defaults={
+                            'marca': vehiculo_data['marca'],
+                            'modelo': vehiculo_data['modelo'],
+                            'foto': vehiculo_data['foto']
+                        }
+                    )
+                    if creado:
+                        vehiculos_creados += 1
+
+                except Vehiculo.DoesNotExist:
+                    self.stderr.write(self.style.ERROR(f"Vehiculo con matrícula '{vehiculo_data['matricula']}' no encontrado."))
+
+            self.stdout.write(self.style.SUCCESS(f'Se importaron {vehiculos_creados} vehiculos correctamente.'))
+
+        except FileNotFoundError:
+            self.stderr.write(self.style.ERROR(f"El archivo {archivo_json} no se encontró."))
+        except json.JSONDecodeError:
+            self.stderr.write(self.style.ERROR("Error al leer el archivo JSON. Asegúrate de que el formato sea correcto."))

+ 27 - 0
src/repostajes/models.py

@@ -0,0 +1,27 @@
+from django.db import models
+from django.core.validators import MaxValueValidator
+
+
+class Vehiculo(models.Model):
+    marca = models.CharField(max_length=200)
+    modelo = models.CharField(max_length=200)
+    matricula = models.CharField(max_length=200)
+    foto = models.ImageField(upload_to='vehiculos/', blank=True, null=True)  # Nuevo campo
+
+    def __str__(self):
+        return self.marca
+
+
+class Repostaje(models.Model):
+    vehiculo = models.ForeignKey(Vehiculo, on_delete=models.CASCADE)
+    fecha = models.DateField()
+    kms = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True)
+    litros = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
+    descuento = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
+    importe = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
+    precioxlitro = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
+    kmsrecorridos = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True)
+    consumo = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
+
+    def __str__(self):
+        return str(self.fecha)

+ 16 - 0
src/repostajes/serializers.py

@@ -0,0 +1,16 @@
+from rest_framework import serializers
+from .models import Vehiculo, Repostaje
+
+
+class VehiculoSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Vehiculo
+        fields = '__all__'  # Incluir todos los campos del modelo
+
+
+class RepostajeSerializer(serializers.ModelSerializer):
+    vehiculo_matricula = serializers.CharField(source='vehiculo.matricula', read_only=True)
+
+    class Meta:
+        model = Repostaje
+        fields = ['id', 'fecha', 'kms', 'litros', 'descuento', 'importe', 'precioxlitro', 'kmsrecorridos', 'consumo', 'vehiculo', 'vehiculo_matricula']

+ 34 - 0
src/repostajes/templatetags/mi_filtro.py

@@ -0,0 +1,34 @@
+from django import template
+from django.contrib.humanize.templatetags.humanize import intcomma
+
+register = template.Library()
+
+def currency_dolares(dollars):
+    dollars = round(float(dollars), 2)
+    return "$ %s%s" % (intcomma(int(dollars)).replace(' ', '.').replace(' ', '.').replace('\xa0', '.'), ("%0.2f" % dollars)[-3:].replace('.', ','))
+
+register.filter('currency_dolares', currency_dolares)
+
+
+def currency_euros(euros):
+    euros = round(float(euros), 2)
+    return "%s%s €" % (intcomma(int(euros)).replace(' ', '.').replace(' ', '.').replace('\xa0', '.'), ("%0.2f" % euros)[-3:].replace('.', ','))
+
+register.filter('currency_euros', currency_euros)
+
+
+def no_currency(numero):
+    numero = round(float(numero), 0)
+    # intcomma normalmente usa comas como separador de miles; además
+    # puede aparecer espacio normal o NBSP. Reemplazamos coma y espacios
+    # por punto para asegurar el formato esperado.
+    return "%s" % (intcomma(int(numero)).replace(',', '.').replace(' ', '.').replace('\xa0', '.'))
+
+register.filter('no_currency', no_currency)
+
+
+def litros(numero):
+    numero = round(float(numero), 2)
+    return "%s" % ("%0.2f" % numero).replace('.', ',')
+
+register.filter('litros', litros)

+ 3 - 0
src/repostajes/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 26 - 0
src/repostajes/urls.py

@@ -0,0 +1,26 @@
+from django.urls import path
+
+from . import views
+from .views import api_lista_vehiculos, api_detalle_vehiculo
+from .views import api_lista_repostajes, api_detalle_repostaje
+
+app_name = 'repostajes'
+
+urlpatterns = [
+    path('', views.principal, name='principal'),
+    path('vehiculos/', views.lista_vehiculos, name='lista_vehiculos'),
+    path('vehiculos/nuevo/', views.nuevo_vehiculo, name='nuevo_vehiculo'),
+    path('vehiculos/<int:vehiculo_id>/', views.detalle_vehiculo, name='detalle_vehiculo'),
+    path('vehiculos/<int:vehiculo_id>/editar/', views.editar_vehiculo, name='editar_vehiculo'),
+    path('vehiculos/<int:vehiculo_id>/eliminar/', views.eliminar_vehiculo, name='eliminar_vehiculo'),
+
+    path('repostajes/', views.lista_repostajes, name='lista_repostajes'),
+    path('repostajes/nuevo/', views.nuevo_repostaje, name='nuevo_repostaje'),
+    path('repostajes/<int:repostaje_id>/', views.detalle_repostaje, name='detalle_repostaje'),
+    path('repostajes/<int:repostaje_id>/editar/', views.editar_repostaje, name='editar_repostaje'),
+    path('repostajes/<int:repostaje_id>/eliminar/', views.eliminar_repostaje, name='eliminar_repostaje'),
+    path('api/vehiculos/', api_lista_vehiculos, name='api_lista_vehiculos'),
+    path('api/vehiculos/<int:vehiculo_id>/', api_detalle_vehiculo, name='api_detalle_vehiculo'),
+    path('api/repostajes/', api_lista_repostajes, name='api_lista_repostajes'),
+    path('api/repostajes/<int:repostaje_id>/', api_detalle_repostaje, name='api_detalle_repostaje'),
+]

+ 194 - 0
src/repostajes/views.py

@@ -0,0 +1,194 @@
+# Create your views here.
+from django.shortcuts import render, get_object_or_404, redirect
+from django.contrib.auth.decorators import login_required
+
+from rest_framework.response import Response
+from rest_framework.decorators import api_view
+
+from .models import Vehiculo, Repostaje
+from .forms import VehiculoForm, RepostajeForm
+from .serializers import VehiculoSerializer, RepostajeSerializer
+
+import logging
+
+logger_info = logging.getLogger('repostajes')
+logger_debug = logging.getLogger('django')
+
+@login_required
+def principal(request):
+    vehiculos = Vehiculo.objects.all()
+    repostajes = Repostaje.objects.all()
+
+    logger_debug.debug("Rendering principal view with %d vehiculos and %d repostajes", vehiculos.count(), repostajes.count())   
+    logger_info.info("User %s accessed principal view", request.user.nombre if request.user.is_authenticated else "Anonymous") 
+
+    return render(request, 'repostajes/index.html', {'vehiculos': vehiculos, 'repostajes': repostajes})
+
+
+# Vistas para los vehiculos
+@login_required
+def lista_vehiculos(request):
+    vehiculos = Vehiculo.objects.all()
+    return render(request, 'repostajes/lista_vehiculos.html', {'vehiculos': vehiculos})
+
+
+@login_required
+def detalle_vehiculo(request, vehiculo_id):
+    vehiculo = get_object_or_404(Vehiculo, pk=vehiculo_id)
+
+    repostajes = Repostaje.objects.filter(vehiculo=vehiculo_id).order_by('-fecha')
+
+    return render(request, 'repostajes/detalle_vehiculo.html', {'vehiculo': vehiculo, 'repostajes': repostajes})
+
+
+@login_required
+def nuevo_vehiculo(request):
+    if request.method == 'POST':
+        form = VehiculoForm(request.POST, request.FILES)
+        if form.is_valid():
+            form.save()
+            return redirect('repostajes:lista_vehiculos')
+    else:
+        form = VehiculoForm()
+    return render(request, 'repostajes/form_vehiculo.html', {'form': form})
+
+
+@login_required
+def editar_vehiculo(request, vehiculo_id):
+    vehiculo = get_object_or_404(Vehiculo, pk=vehiculo_id)
+    if request.method == 'POST':
+        form = VehiculoForm(request.POST, request.FILES, instance=vehiculo)
+        if form.is_valid():
+            form.save()
+            return redirect('repostajes:lista_vehiculos')
+    else:
+        form = VehiculoForm(instance=vehiculo)
+    return render(request, 'repostajes/form_vehiculo.html', {'form': form})
+
+
+@login_required
+def eliminar_vehiculo(request, vehiculo_id):
+    vehiculo = get_object_or_404(Vehiculo, pk=vehiculo_id)
+    vehiculo.delete()
+    return redirect('repostajes:lista_vehiculos')
+
+
+# Vistas para los repostajes
+@login_required
+def lista_repostajes(request):
+    repostajes = Repostaje.objects.all().order_by('-fecha')
+    return render(request, 'repostajes/lista_repostajes.html', {'repostajes': repostajes})
+
+
+@login_required
+def detalle_repostaje(request, repostaje_id):
+    repostaje = get_object_or_404(Repostaje, pk=repostaje_id)
+    return render(request, 'repostajes/detalle_repostaje.html', {'repostaje': repostaje})
+
+
+@login_required
+def nuevo_repostaje(request):
+
+    vehiculos = Vehiculo.objects.all()  # vamos a ver si hay vehículos dados de alta
+
+    if vehiculos:
+        if request.method == 'POST':
+            form = RepostajeForm(request.POST, request.FILES)
+            if form.is_valid():
+                instancia = form.save(commit=False)
+
+                aplica_descuento = form.cleaned_data['aplica_descuento']
+
+                if aplica_descuento:
+                    instancia.descuento = float(instancia.importe) * 0.03
+                else:
+                    instancia.descuento = 0.0
+
+                instancia.importe = float(instancia.importe) - instancia.descuento
+
+                if instancia.litros > 0:
+                    instancia.precioxlitro = round(instancia.importe / float(instancia.litros), 2)
+                else:
+                    instancia.precioxlitro = 0
+
+                # lee todos los repostajes del vehículo
+                # repostajes = Repostajes.query.filter_by(vehiculo_id=vehiculo_id).all()
+
+                if Repostaje.objects.filter(vehiculo_id=instancia.vehiculo):
+                    repostajes = Repostaje.objects.filter(vehiculo_id=instancia.vehiculo).order_by('-fecha')[0]
+
+                    instancia.kmsrecorridos = instancia.kms - repostajes.kms
+
+                    if instancia.kmsrecorridos > 0:
+                        instancia.consumo = round(instancia.litros * 100 / instancia.kmsrecorridos, 2)
+                else:
+                    instancia.kmsrecorridos = 0
+                    instancia.consumo = 0
+
+                instancia.save()
+
+                return redirect('repostajes:lista_repostajes')
+        else:
+            form = RepostajeForm()
+        return render(request, 'repostajes/form_repostaje.html', {'form': form})
+    else:
+        return render(request, 'repostajes/index.html')
+
+
+@login_required
+def editar_repostaje(request, repostaje_id):
+    repostaje = get_object_or_404(Repostaje, pk=repostaje_id)
+
+    if request.method == 'POST':
+        form = RepostajeForm(request.POST, request.FILES, instance=repostaje)
+        if form.is_valid():
+            form.save()
+            return redirect('repostajes:lista_repostajes')
+    else:
+        form = RepostajeForm(instance=repostaje)
+    return render(request, 'repostajes/form_repostaje.html', {'form': form})
+
+
+@login_required
+def eliminar_repostaje(request, repostaje_id):
+    repostaje = Repostaje.objects.get(pk=repostaje_id)
+    repostaje.delete()
+    return redirect('repostajes:lista_repostajes')
+
+
+@api_view(['GET'])
+def api_lista_vehiculos(request):
+    """Devuelve la lista de todos los vehiculos."""
+    vehiculos = Vehiculo.objects.all()
+    serializer = VehiculoSerializer(vehiculos, many=True)
+    return Response(serializer.data)
+
+
+@api_view(['GET'])
+def api_detalle_vehiculo(request, vehiculo_id):
+    """Devuelve los detalles de un vehiculo específico."""
+    try:
+        vehiculo = Vehiculo.objects.get(id=vehiculo_id)
+        serializer = VehiculoSerializer(vehiculo)
+        return Response(serializer.data)
+    except Vehiculo.DoesNotExist:
+        return Response({'error': 'Vehiculo no encontrado'}, status=404)
+
+
+@api_view(['GET'])
+def api_lista_repostajes(request):
+    """Devuelve la lista de todos los repostajes."""
+    repostajes = Repostaje.objects.all()
+    serializer = RepostajeSerializer(repostajes, many=True)
+    return Response(serializer.data)
+
+
+@api_view(['GET'])
+def api_detalle_repostaje(request, repostaje_id):
+    """Devuelve los detalles de un repostaje específico."""
+    try:
+        repostaje = Repostaje.objects.get(id=repostaje_id)
+        serializer = RepostajeSerializer(repostaje)
+        return Response(serializer.data)
+    except Repostaje.DoesNotExist:
+        return Response({'error': 'Repostaje no encontrado'}, status=404)

+ 14 - 0
src/templates/base.html

@@ -150,6 +150,20 @@
                              <span class="nav-link-text">Ver cuentas</span>
                         </a><!--//nav-link-->
                     </li><!--//nav-item-->
+                    
+                    <li class="nav-item">
+                        <!--//Bootstrap Icons: https://icons.getbootstrap.com/ -->
+                        <a class="nav-link" href="{% url 'repostajes:principal' %}">
+                            <span class="nav-icon">
+                            <!--//Bootstrap Icons: https://icons.getbootstrap.com/ -->
+                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-car-front" viewBox="0 0 16 16">
+                                <path d="M4 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0m10 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0M6 8a1 1 0 0 0 0 2h4a1 1 0 1 0 0-2zM4.862 4.276 3.906 6.19a.51.51 0 0 0 .497.731c.91-.073 2.35-.17 3.597-.17s2.688.097 3.597.17a.51.51 0 0 0 .497-.731l-.956-1.913A.5.5 0 0 0 10.691 4H5.309a.5.5 0 0 0-.447.276"/>
+                                <path d="M2.52 3.515A2.5 2.5 0 0 1 4.82 2h6.362c1 0 1.904.596 2.298 1.515l.792 1.848c.075.175.21.319.38.404.5.25.855.715.965 1.262l.335 1.679q.05.242.049.49v.413c0 .814-.39 1.543-1 1.997V13.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-1.338c-1.292.048-2.745.088-4 .088s-2.708-.04-4-.088V13.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-1.892c-.61-.454-1-1.183-1-1.997v-.413a2.5 2.5 0 0 1 .049-.49l.335-1.68c.11-.546.465-1.012.964-1.261a.8.8 0 0 0 .381-.404l.792-1.848ZM4.82 3a1.5 1.5 0 0 0-1.379.91l-.792 1.847a1.8 1.8 0 0 1-.853.904.8.8 0 0 0-.43.564L1.03 8.904a1.5 1.5 0 0 0-.03.294v.413c0 .796.62 1.448 1.408 1.484 1.555.07 3.786.155 5.592.155s4.037-.084 5.592-.155A1.48 1.48 0 0 0 15 9.611v-.413q0-.148-.03-.294l-.335-1.68a.8.8 0 0 0-.43-.563 1.8 1.8 0 0 1-.853-.904l-.792-1.848A1.5 1.5 0 0 0 11.18 3z"/>
+                            </svg>
+                             </span>
+                             <span class="nav-link-text">Repostajes</span>
+                        </a><!--//nav-link-->
+                    </li><!--//nav-item-->
 
                     {% endif %}
 

+ 7 - 0
src/templates/repostajes/_menu-repostajes.html

@@ -0,0 +1,7 @@
+<div class="app-utility-item">
+  <a class="btn btn-primary" href="{% url 'repostajes:lista_vehiculos' %}" role="button">Vehículos</a>
+</div><!--//app-utility-item-->
+
+<div class="app-utility-item">
+  <a class="btn btn-primary" href="{% url 'repostajes:lista_repostajes' %}" role="button">Repostajes</a>
+</div><!--//app-utility-item-->

+ 37 - 0
src/templates/repostajes/detalle_repostaje.html

@@ -0,0 +1,37 @@
+{% extends 'base.html' %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+<div class="container-xl">
+    <div class="app-card app-card-notification shadow-sm mb-4">
+        <div class="app-card-header px-4 py-3">
+            <div class="row g-3 align-items-center">
+                <div class="col-12 col-lg-auto text-center text-lg-start">
+                    <h4 class="notification-title mb-1">{{ repostaje.fecha }}</h4>
+                    
+                    <ul class="notification-meta list-inline mb-0">
+                        <li class="list-inline-item"><a href="{% url 'repostajes:detalle_vehiculo' repostaje.vehiculo_id %}">{{ repostaje.vehiculo.matricula }}</a></li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.kms }} kms</li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.litros }} litros</li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.importe }} €</li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.kmsrecorridos }} kms. recorridos</li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.consumo }} litros/100 kms</li>
+                        <li class="list-inline-item">|</li>
+                        <li class="list-inline-item">{{ repostaje.precioxlitro }} €/litros</li>
+                    </ul>
+                </div><!--//col-->
+            </tr>
+            </div><!--//row-->
+        </div><!--//app-card-header-->
+    </div><!--//app-card-->
+{% endblock %}

+ 82 - 0
src/templates/repostajes/detalle_vehiculo.html

@@ -0,0 +1,82 @@
+{% extends 'base.html' %}
+
+{% load humanize %}
+
+{% load mi_filtro %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+<div class="container-xl">
+    <div class="app-card app-card-notification shadow-sm mb-4">
+        <div class="app-card-header px-4 py-3">
+            <div class="row g-3 align-items-center">
+                <div class="col-12 col-lg-auto text-center text-lg-start">
+                    {% if vehiculo.foto %}
+                    <p><img src="{{ vehiculo.foto.url }}" alt="{{ vehiculo.matricula}}" width="187" height="115"></p>
+                    {% else %}
+                    <p>No hay imágen disponible</p>
+                    {% endif %}
+                </div>
+                <div class="col-12 col-lg-auto text-center text-lg-start">
+                    <h4>{{ vehiculo.matricula }}</h4>
+
+                    <ul class="notification-meta list-inline mb-0">
+                        <li class="list-inline-item">{{ vehiculo.marca }} {{ vehiculo.modelo }}</li>
+                    </ul>
+                </div>
+            </div>
+        </div>
+        <div class="app-card-body p-4">
+
+            {% if repostajes %}
+                <table class="table app-table-hover mb-0 text-left">
+                    <thead>
+                        <tr>
+                            <th class="cell">Fecha</th>
+                            <th class="cell">Kilómetros</th>
+                            <th class="cell">Litros</th>
+                            <th class="cell">Importe</th>
+                            <th class="cell">Descuento</th>
+                            <th class="cell">Precio por litro</th>
+                            <th class="cell">Kms recorridos</th>
+                            <th class="cell">Consumo/100 kms</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {% for repostaje in repostajes %}
+                        <tr>
+                            <td class="cell"><a href="{% url 'repostajes:detalle_repostaje' repostaje.id %}">{{ repostaje.fecha }}</a></td>
+                            <td class="cell">{{ repostaje.kms | no_currency }}</td>
+                            <td class="text-end">{{ repostaje.litros | litros }}</td>
+                            <td class="text-end">{{ repostaje.importe | currency_euros  }}</td>
+                            <td class="text-end">{{ repostaje.descuento | currency_euros  }}</td>
+                            <td class="text-end">{{ repostaje.precioxlitro | currency_euros  }}</td>
+                            <td class="text-end">{{ repostaje.kmsrecorridos | no_currency }}</td>
+                            <td class="text-end">{{ repostaje.consumo | litros }}</td>
+                        </tr>
+                        {% endfor %}	
+                    </tbody>
+                </table>
+            {% else %}
+                <p>No se han encontrado repostajes para este vehiculo</p>
+            {% endif %}
+        </div>
+    </div>
+
+    <div class="row g-3 mb-4 align-items-center justify-content-between">
+        <div class="col-auto">
+            <a class="btn app-btn-secondary" href="{% url 'repostajes:lista_repostajes' %}">Volver al inicio</a>
+        </div>
+
+        <div class="col-auto">
+            <a class="btn app-btn-primary" href="{% url 'repostajes:nuevo_repostaje' %}">Añadir repostaje</a> <!-- Faltaría poner el id del vehiculo-->
+        </div>
+    </div>
+</div>
+{% endblock %}
+

+ 23 - 0
src/templates/repostajes/form_repostaje.html

@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+<div class="column is-4 is-offset-4">
+    <h3>{% if form.instance.pk %}Editar repostaje{% else %}Nuevo repostaje{% endif %}</h3>
+    <div class="box">
+        <form method="POST"  enctype="multipart/form-data"> 
+            {% csrf_token %}
+            {{ form.as_p }}
+            <div class="text mb-3">
+                  <button type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto">Guardar</button>
+            </div>
+        </form>
+	{{ form.media }}
+    </div>
+</div>
+{% endblock %}

+ 23 - 0
src/templates/repostajes/form_vehiculo.html

@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+<div class="column is-4 is-offset-4">
+
+    <h3>{% if form.instance.pk %}Editar vehículo{% else %}Nuevo vehículo{% endif %}</h3>
+    <div class="box">
+        <form method="POST" enctype="multipart/form-data">
+            {% csrf_token %}
+            {{ form.as_p }}
+            <div class="text mb-3">
+                <button type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto">Guardar</button>
+            </div>
+        </form>
+    </div>
+</div>
+{% endblock %}

+ 33 - 0
src/templates/repostajes/index.html

@@ -0,0 +1,33 @@
+{% extends 'base.html' %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+
+<div class="container-xl">
+			    
+    <h1 class="app-page-title">Introducción</h1>
+    
+    <div class="app-card alert alert-dismissible shadow-sm mb-4 border-left-decoration" role="alert">
+        <div class="inner">
+            <div class="app-card-body p-3 p-lg-4">
+                <h3 class="mb-3">¡Bienvenido a la gestión de vehículos!</h3>
+                <div class="row gx-5 gy-3">
+                    <!--
+                    <div class="col-12 col-lg-9">
+                        
+                        <div>Pensado inicialmente para guardar las letras de las canciones de Bruce Springsteen.</div>
+                    </div>--><!--//col-->
+                </div><!--//row-->
+                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+            </div><!--//app-card-body-->            
+        </div><!--//inner-->
+    </div><!--//app-card-->
+
+</div><!--//container-fluid-->
+
+{% endblock %}

+ 66 - 0
src/templates/repostajes/lista_repostajes.html

@@ -0,0 +1,66 @@
+{% extends 'base.html' %}
+
+{% load humanize %}
+
+{% load mi_filtro %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+		    <div class="container-xl">
+			    <div class="row g-3 mb-4 align-items-center justify-content-between">
+				    <div class="col-auto">
+			            <h1 class="app-page-title mb-0">Repostajes</h1>
+				    </div>
+			    </div><!--//row-->				
+				<div class="col-auto">
+					<div class="page-utilities">
+					    <div class="row g-4 justify-content-start justify-content-md-end align-items-center">
+						    <div class="col-auto">
+							   <a class="btn app-btn-primary" href="{% url 'repostajes:nuevo_repostaje' %}">Añadir repostaje</a>
+						    </div>
+					    </div><!--//row-->
+				    </div><!--//table-utilities-->
+			    </div><!--//col-auto-->
+
+
+				<div class="app-card app-card-notification shadow-sm mb-4">
+					<div class="app-card-body p-4">
+									<table class="table app-table-hover mb-0 text-left">
+										<thead>
+											<tr>
+												<th class="cell">Fecha</th>
+												<th class="cell">Vehículo</th>
+												<th class="cell">Kilómetros</th>
+												<th class="cell">Litros</th>
+												<th class="cell">Importe</th>
+												<th class="cell">Descuento</th>
+												<th class="cell">Precio por litro</th>
+												<th class="cell">Kms recorridos</th>
+												<th class="cell">Consumo/100 kms</th>
+											</tr>
+										</thead>
+										{% for repostaje in repostajes %}
+
+										<tbody>
+											<tr>
+												<td class="cell"><a href="{% url 'repostajes:detalle_repostaje' repostaje.id %}">{{ repostaje.fecha }}</a></td>
+												<td class="cell"><a href="{% url 'repostajes:detalle_vehiculo' repostaje.vehiculo.id %}">{{ repostaje.vehiculo.matricula }}</a></td>
+												<td class="text-end">{{ repostaje.kms | no_currency }}</td>
+												<td class="text-end">{{ repostaje.litros | litros }}</td>
+												<td class="text-end">{{ repostaje.importe | currency_euros }}</td>
+												<td class="text-end">{{ repostaje.descuento | currency_euros  }}</td>
+												<td class="text-end">{{ repostaje.precioxlitro | currency_euros  }}</td>
+												<td class="text-end">{{ repostaje.kmsrecorridos | no_currency }}</td>
+												<td class="text-end">{{ repostaje.consumo | litros}}</td>
+											</tr>
+										</tbody>
+										{% endfor %}		
+									</table>
+				</div>
+		    </div><!--//container-fluid-->
+{% endblock %}

+ 77 - 0
src/templates/repostajes/lista_vehiculos.html

@@ -0,0 +1,77 @@
+{% extends 'base.html' %}
+
+{% block menuapp %}
+
+{% include 'repostajes/_menu-repostajes.html' %}
+
+{% endblock menuapp %}
+
+{% block content %}
+		    <div class="container-xl">
+			    
+			    <div class="row g-3 mb-4 align-items-center justify-content-between">
+				    <div class="col-auto">
+			            <h1 class="app-page-title mb-0">Vehículos</h1>
+				    </div>
+			    </div><!--//row-->				
+				<div class="col-auto">
+					<div class="page-utilities">
+					    <div class="row g-4 justify-content-start justify-content-md-end align-items-center">
+						    <div class="col-auto">
+							   <a class="btn app-btn-primary" href="{% url 'repostajes:nuevo_vehiculo' %}">Añadir vehiculo</a>
+						    </div>
+					    </div><!--//row-->
+				    </div><!--//table-utilities-->
+			    </div><!--//col-auto-->
+
+			    <div class="row g-4">
+					{% for vehiculo in vehiculos %}
+				    <div class="col-6 col-md-4 col-xl-3 col-xxl-2">
+						<div class="app-card app-card-doc shadow-sm h-100">    
+							<div class="app-card-body p-3 has-card-actions">
+							    
+                                {% if vehiculo.foto %}
+                                <img src="{{ vehiculo.foto.url }}" alt="Foto del vehiculo" width="187px" height="115px">
+						        {% else %}
+									Sin imágen
+								{% endif %}
+							    <h4 class="app-doc-title truncate mb-0"><a href="{% url 'repostajes:detalle_vehiculo' vehiculo.id %}">{{ vehiculo.matricula}}</a></h4>
+							    
+							    <div class="app-card-actions">
+								    <div class="dropdown">
+									    <div class="dropdown-toggle no-toggle-arrow" data-bs-toggle="dropdown" aria-expanded="false">
+										    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-three-dots-vertical" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+			  <path fill-rule="evenodd" d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
+			</svg>
+									    </div><!--//dropdown-toggle-->
+									    <ul class="dropdown-menu">
+										    <li><a class="dropdown-item" href="{% url 'repostajes:detalle_vehiculo' vehiculo.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-eye me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	  <path fill-rule="evenodd" d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.134 13.134 0 0 0 1.66 2.043C4.12 11.332 5.88 12.5 8 12.5c2.12 0 3.879-1.168 5.168-2.457A13.134 13.134 0 0 0 14.828 8a13.133 13.133 0 0 0-1.66-2.043C11.879 4.668 10.119 3.5 8 3.5c-2.12 0-3.879 1.168-5.168 2.457A13.133 13.133 0 0 0 1.172 8z"/>
+	  <path fill-rule="evenodd" d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
+	</svg>Ver</a></li>
+											<li><a class="dropdown-item" href="{% url 'repostajes:editar_vehiculo' vehiculo.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	  <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
+	</svg>Editar</a></li>
+											<li><a class="dropdown-item" href="{% url 'repostajes:eliminar_vehiculo' vehiculo.id %}"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	  <path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
+	  <path fill-rule="evenodd" d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
+	</svg>Eliminar</a></li>
+    	<!--
+
+	                                        <li><hr class="dropdown-divider"></li>
+											<li><a class="dropdown-item" href="#"><svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-trash me-2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+	  <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
+	  <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
+	</svg>Delete</a></li>
+	-->
+										</ul>
+								    </div><!--//dropdown-->
+						        </div><!--//app-card-actions-->
+								    
+						    </div><!--//app-card-body-->
+						</div>
+					</div>
+					{% endfor %}		
+				</div>
+		    </div><!--//container-fluid-->
+{% endblock %}