git pyfleet en gitea.xiberus

This commit is contained in:
2026-08-28 19:30:48 +02:00
commit 89992823b4
2133 changed files with 27616 additions and 0 deletions
+805
View File
@@ -0,0 +1,805 @@
{% extends "base.html" %}
{% block title %}{{ 'Editar' if vehiculo else 'Nuevo' }} Vehículo{% endblock %}
{% block extra_css %}
.form-card { max-width: 860px; }
.section-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--accent);
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
margin: 28px 0 20px;
}
.section-title:first-of-type { margin-top: 0; }
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.col-full { grid-column: 1 / -1; }
.col-2 { grid-column: span 2; }
{% endblock %}
{% block content %}
<div class="page-header">
<div class="page-eyebrow">Vehículos</div>
<div class="page-title">{{ 'Editar vehículo ' ~ vehiculo['m_oficial'] if vehiculo else 'Nuevo vehículo' }}</div>
<div class="page-subtitle">
{{ 'Modifica los datos del vehículo.' if vehiculo else 'Completa el formulario para registrar un nuevo vehículo.' }}
</div>
</div>
<div class="form-card">
<form method="POST">
<input type="hidden" name="_q" value="{{ q|default('') }}">
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
<input type="hidden" name="_q_itv" value="{{ q_itv|default('') }}">
<input type="hidden" name="_origen" value="{{ origen|default('') }}">
<input type="hidden" name="_q_viat" value="{{ q_viat|default('') }}">
{# ── IDENTIFICACIÓN ── #}
<div class="section-title">Identificación {{ '- id' ~ vehiculo['id'] if vehiculo }}</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label" for="m_oficial">Matrícula oficial</label>
<input class="form-input" type="text" id="m_oficial" name="m_oficial"
placeholder="Ej. 1234ABC" required
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['m_oficial'] or '') if vehiculo else request.form.get('m_oficial', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="m_civil">Matrícula civil</label>
<input class="form-input" type="text" id="m_civil" name="m_civil"
placeholder="Ej. 5678XYZ"
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['m_civil'] or '') if vehiculo else request.form.get('m_civil', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="m_propia">Matrícula propia</label>
<input class="form-input" type="text" id="m_propia" name="m_propia"
placeholder="Ej. 0123ZXZ"
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['m_propia'] or '') if vehiculo else request.form.get('m_propia', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="VIN">VIN</label>
<input class="form-input" type="text" id="VIN" name="VIN"
placeholder="VIN"
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['VIN'] or '') if vehiculo else request.form.get('VIN', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="poliza">Poliza</label>
<input class="form-input" type="text" id="poliza" name="poliza"
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['Poliza'] or '') if vehiculo else request.form.get('poliza', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="viat">Via-T</label>
<input class="form-input" type="text" id="viat" name="viat" style="width: 210px; padding-left: 10px;"
oninput="this.value = this.value.toUpperCase();"
value="{{ (vehiculo['viat'] or '') if vehiculo else request.form.get('viat', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="legajo">Legajo</label>
<input class="form-input" id="legajo" name="legajo"
placeholder=""
value="{{ (vehiculo['legajo'] or '') if vehiculo else request.form.get('viat', '') }}">
</div>
</div>
{# ── CARACTERÍSTICAS TÉCNICAS ── #}
<div class="section-title">Características técnicas</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label" for="marca">Marca</label>
<input class="form-input" type="text" id="marca" name="marca"
placeholder="Ej. Ford, Renault…" required
value="{{ (vehiculo['marca'] or '') if vehiculo else request.form.get('marca', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="modelo">Modelo</label>
<input class="form-input" type="text" id="modelo" name="modelo"
placeholder="Ej. Transit, Kangoo…" required
value="{{ (vehiculo['modelo'] or '') if vehiculo else request.form.get('modelo', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="tipo">Tipo vehículo</label>
<div class="select-wrap">
<select class="form-input form-select" id="tipo" name="tipo" required>
<option value="">— Selecciona tipo —</option>
{% for tip in tipo %}
<option value="{{ tip['tipo'] }}"
{% if vehiculo and vehiculo['tipo'] == tip['tipo'] %}selected
{% elif request.form.get('tipo') == tip['tipo'] %}selected
{% endif %}>
{{ tip['tipo'] }}
</option>
{% endfor %}
</select>
<svg class="select-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>
<div class="form-group">
<label class="form-label" for="color">Color</label>
<input class="form-input" type="text" id="color" name="color"
placeholder="Ej. Blanco, Negro…"
value="{{ (vehiculo['color'] or '') if vehiculo else request.form.get('color', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="cilindrada">Cilindrada</label>
<input class="form-input" type="text" id="cilindrada" name="cilindrada"
placeholder="Ej. 1600cc"
value="{{ (vehiculo['cilindrada'] or '') if vehiculo else request.form.get('cilindrada', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="potencia">Potencia (CV)</label>
<input class="form-input" type="number" id="potencia" name="potencia"
placeholder="Ej. 120"
value="{{ (vehiculo['potencia'] or '') if vehiculo else request.form.get('potencia', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="combustible">Combustible</label>
<input class="form-input" type="text" id="combustible" name="combustible"
placeholder="Gasolina / Diésel / Eléctrico…"
value="{{ (vehiculo['combustible'] or '') if vehiculo else request.form.get('combustible', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="neumaticos">Neumáticos</label>
<input class="form-input" type="text" id="neumaticos" name="neumaticos"
placeholder="Ej. 205/55 R16"
value="{{ (vehiculo['neumaticos'] or '') if vehiculo else request.form.get('neumaticos', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="plazas">Plazas</label>
<input class="form-input" type="text" id="plazas" name="plazas"
placeholder="Nº de plazas"
value="{{ (vehiculo['plazas'] or '') if vehiculo else request.form.get('plazas', '') }}">
</div>
<div class="form-group" style="width: 280px;">
<label class="form-label"> </label>
<label style="margin-top:22px;display:flex;align-items:center;gap:10px;cursor:pointer;
padding:10px 14px;background:var(--bg);border:1px solid var(--border);
border-radius:8px;transition:border-color .15s;"
onmouseover="this.style.borderColor='var(--accent)'"
onmouseout="this.style.borderColor='var(--border)'">
<input type="checkbox" name="lanzadestellos" value="1"
{% if vehiculo and vehiculo['lanzadestellos'] == 1 %}checked{% endif %}
style="width:16px;height:16px;accent-color:var(--accent);cursor:pointer;">
<span style="font-size:13px;color:var(--text)">Lanzadestellos</span>
<span style="font-size:11px;color:var(--muted);margin-left:4px;">
({{ '✓ Con lanzadestellos' if vehiculo and vehiculo['lanzadestellos'] == 1 else '✗ Sin lanzadestellos' }})
</span>
</label>
</div>
</div>
{% if vehiculo %}
{% set sit = vehiculo['situacion'] or '' %}
{% if sit | upper == 'BAJA' %}
{% set color_borde = '#f87171' %}
{% set color_fondo = 'rgba(248,113,113,.08)' %}
{% elif sit | upper == 'ACTIVO' %}
{% set color_borde = '#34d399' %}
{% set color_fondo = 'rgba(52,211,153,.08)' %}
{% elif sit | upper == 'ALTA' %}
{% set color_borde = '#60a5fa' %}
{% set color_fondo = 'rgba(96,165,250,.08)' %}
{% else %}
{% set color_borde = 'var(--border)' %}
{% set color_fondo = 'var(--pill-bg)' %}
{% endif %}
{% set estilo_sit = 'background:' ~ color_fondo ~ ';border:1.5px solid ' ~ color_borde ~ ';color:var(--text);font-weight:600;cursor: not-allowed;' %}
{# ── SITUACIÓN Y DESTINO ── #}
<div class="section-title">Situación y destino (No editable)</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label" for="situacion">Situación</label>
<input class="form-input" type="text" id="situacion" name="situacion" style="{{ estilo_sit }}" readonly
value="{{ (vehiculo['situacion'] or '') if vehiculo else request.form.get('situacion', '') }}">
</div>
<div class="form-group">
<label class="form-label" for="poblacion">Población</label>
<input class="form-input" type="text" id="poblacion" name="poblacion" style="{{ estilo_sit }}" readonly
value="{{ (vehiculo['poblacion'] or '') if vehiculo else request.form.get('poblacion','') }}">
</div>
<div class="form-group" style="width: 400px">
<label class="form-label" for="unidad">Unidad</label>
<input class="form-input" type="text" id="unidad" name="unidad"
style="{{ estilo_sit }}" readonly
value="{{ (vehiculo['unidad'] or '') if vehiculo else request.form.get('unidad','') }}">
</div>
</div>
{# ── FECHAS Y KILOMETRAJE ── #}
<div class="section-title">Kilometraje - ITV</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label" for="km">Km actuales</label>
<input class="form-input" id="km" name="km"
placeholder=""
value="{{ (vehiculo['km'] or '') if vehiculo else request.form.get('km','') }}">
<!-- value="{{ vehiculo['km'] if vehiculo else request.form.get('km','') }}"> -->
</div>
<div class="form-group">
<label class="form-label" for="km_1">Km anterior</label>
<input class="form-input" id="km_1" name="km_1"
placeholder=""
value="{{ (vehiculo['km_1'] or '') if vehiculo else request.form.get('km_1','') }}">
</div>
<div class="form-group">
<label class="form-label" for="itv_ultima">Última ITV</label>
<input class="form-input" type="date" id="itv_ultima" name="itv_ultima"
value="{{ (vehiculo['itv_ultima'][:10] or '') if vehiculo and vehiculo['itv_ultima'] else request.form.get('itv_ultima','') }}">
</div>
<div class="form-group">
<label class="form-label" for="itv">Próxima ITV</label>
<input class="form-input" type="date" id="itv" name="itv"
value="{{ (vehiculo['itv'][:10] or '') if vehiculo and vehiculo['itv'] else request.form.get('itv','') }}">
</div>
</div>
{# ── NOTAS ── #}
<div class="section-title">NOTAS</div>
<div class="form-group" style="max-width: 1560px;">
<!-- <textarea name="observaciones">{{ (vehiculo['observaciones'] or '') if vehiculo else '' }}</textarea> -->
<textarea class="form-input" id="notas" name="notas"
rows="5" placeholder="Notas adicionales…">{{ (vehiculo['notas'] or '') if vehiculo else request.form.get('notas','') }}
</textarea>
</div>
{# ── DATOS INTERVENIDO ── #}
<!-- {% set m_oficial = vehiculo['m_oficial'] if vehiculo else '' %} -->
<div id="bloque-intervenido"
style="display:{{ 'block' if m_oficial and m_oficial.strip().upper().endswith('Q') else 'none' }}">
<div class="section-title" style="margin-top:28px">Datos vehículo intervenido</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label" for="int_operacion">Operacion</label>
<input class="form-input" id="int_operacion" name="int_operacion"
placeholder=""
value="{{ (vehiculo['int_operacion'] or '') if vehiculo else request.form.get('int_operacion','') }}">
</div>
<div class="form-group">
<label class="form-label" for="int_grupo">Grupo interviene</label>
<input class="form-input" id="int_grupo" name="int_grupo"
placeholder=""
value="{{ (vehiculo['int_grupo'] or '') if vehiculo else request.form.get('int_grupo','') }}">
</div>
<div class="form-group">
<label class="form-label" for="int_procedimiento">Diligencias</label>
<input class="form-input" id="int_procedimiento" name="int_procedimiento"
placeholder=""
value="{{ (vehiculo['int_procedimiento'] or '') if vehiculo else request.form.get('int_procedimiento','') }}">
</div>
<div class="form-group">
<label class="form-label" for="int_juzgado">Juzgado interviene</label>
<input class="form-input" id="int_juzgado" name="int_juzgado"
placeholder=""
value="{{ (vehiculo['int_juzgado'] or '') if vehiculo else request.form.get('int_juzgado','') }}">
</div>
</div>
</div>
{% endif %}
<div class="form-actions" style="margin-top:28px">
<button type="submit" class="btn btn-primary">
{{ '💾 GUARDAR CAMBIOS' if vehiculo else '✚ Registrar vehículo' }}
</button>
{# Cancelar #}
{% if origen == 'viat' %}
<a href="/viat?q={{ q_viat }}" class="btn btn-ghost">Cancelar</a>
{% elif desde or hasta or q_itv %}
<a href="/itv?desde={{ desde }}&hasta={{ hasta }}&q_itv={{ q_itv }}" class="btn btn-ghost">Cancelar</a>
{% else %}
<a href="{{ url_for('vehiculos_list', q=q) if q else url_for('vehiculos_list') }}" class="btn btn-ghost">Cancelar</a>
{% endif %}
<!-- <div style="display:flex;justify-content:space-between;align-items:center;margin-top:12px;"> -->
{% if vehiculo %}
<button type="button" class="btn btn-ghost" onclick="imprimirFicha()" title="Imprimir ficha del vehículo">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 6 2 18 2 18 9"/>
<path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/>
<rect x="6" y="14" width="12" height="8"/>
</svg>
Ficha - Imprimir/PDF
</button>
{% endif %}
<!-- </div> -->
</div>
</form> {# ← CIERRE del formulario principal #}
<!-- {# Eliminar vehículo FUERA del form principal #}
{% if vehiculo %}
{% set nombre_vehiculo = vehiculo['m_oficial'] or vehiculo['tipo'] or '#' ~ vehiculo['id'] %} -->
<!-- QUITADO EL BOTON ELIMINAR -->
<!-- <form method="POST"
action="{{ url_for('vehiculos_eliminar', id=vehiculo['id']) }}"
style="margin-top:0px;display:flex;justify-content:flex-end;">
<input type="hidden" name="_q" value="{{ q|default('') }}">
<button type="button" class="btn btn-danger"
onclick="confirmarEliminarVehiculo(this)">🗑 Eliminar vehículo</button>
</form> -->
<!-- {% endif %} -->
{# ── HISTORIAL (fuera del form principal) ── #}
{% if vehiculo %}
<div class="section-title" style="margin-top:32px">Historial</div>
{% if historial %}
<div class="table-wrap" style="margin-bottom:20px;">
<table>
<thead>
<tr>
<th>ID</th>
<th>Fecha</th>
<th>Matrícula</th>
<th>Situación</th>
<th>Poblacion</th>
<th>Unidad</th>
<th>Observaciones</th>
<th style="text-align:right">Acción</th>
</tr>
</thead>
<tbody>
{% for h in historial %}
<tr>
<td class="td-id">{{ h['id'] or '—' }}</td>
<td style="white-space:nowrap">{{ h['fecha'] | fecha_es }}</td>
<!-- <td style="white-space:nowrap">{{ h['fecha'] or '—' }}</td> -->
<td>{{ h['m_oficial'] or '—' }}</td>
<td>{{ h['situacion'] or '—' }}</td>
<td>{{ h['poblacion'] or '—' }}</td>
<td>{{ h['unidad'] or '—' }}</td>
<!-- <td>{{ h['grupo'] or '—' }}</td> -->
<td style="color:var(--muted);font-size:12px">{{ h['observaciones'] or '—' }}</td>
<td style="text-align:right">
<form method="POST"
action="{{ url_for('historial_eliminar', vid=vehiculo['id'], hid=h['id']) }}">
<input type="hidden" name="_q" value="{{ q|default('') }}">
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
<input type="hidden" name="_unidad_itv" value="{{ unidad_itv|default('') }}">
<button type="button" class="btn btn-danger btn-sm"
onclick="confirmarEliminar(this)">Eliminar</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p style="color:var(--muted);font-size:13px;margin-bottom:20px;">
No hay entradas en el historial de este vehículo.
</p>
{% endif %}
{# Formulario añadir entrada #}
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:20px;">
<p style="font-size:11px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;
color:var(--muted);margin-bottom:16px;">Añadir entrada al historial</p>
<form method="POST" action="{{ url_for('historial_nuevo', vid=vehiculo['id']) }}">
<input type="hidden" name="_q" value="{{ q|default('') }}">
<input type="hidden" name="_desde" value="{{ desde|default('') }}">
<input type="hidden" name="_hasta" value="{{ hasta|default('') }}">
<input type="hidden" name="_unidad_itv" value="{{ unidad_itv|default('') }}">
<div class="form-grid" style="margin-bottom:12px;">
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="h_fecha">Fecha</label>
<input class="form-input" type="date" id="h_fecha" name="h_fecha" required>
</div>
<input type='hidden' id="h_vehiculo_id" name="h_vehiculo_id" readonly
placeholder="" value="{{ vehiculo['id'] if vehiculo else request.form.get('id','') }}">
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="h_situacion">Situación</label>
<div class="select-wrap">
<select class="form-input form-select" id="h_situacion" name="h_situacion"
onchange="onSituacionChange(this.value)" required>
<option value="">— Selecciona situación —</option>
<option value="ACTIVO">ACTIVO</option>
<!-- <option value="EN REPARACIÓN">EN REPARACIÓN</option> -->
<option value="ALTA">ALTA</option>
<option value="BAJA">BAJA</option>
</select>
<svg class="select-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>
<div class="form-group" style="margin-bottom:0">
<label class="form-label" for="h_poblacion">Población</label>
<div class="select-wrap">
<select class="form-input form-select" id="h_poblacion" name="h_poblacion"
onchange="cargarUnidades(this.value)" disabled>
<option value="">— Selecciona población —</option>
{% for p in poblaciones %}
<option value="{{ p['id'] }}">{{ p['poblacion'] }}</option>
{% endfor %}
</select>
<svg class="select-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>
<div class="form-group col-2" style="margin-bottom:0">
<label class="form-label" for="h_unidad">Unidad</label>
<div class="select-wrap">
<select class="form-input form-select" id="h_unidad" name="h_unidad" disabled>
<option value="">— Primero selecciona población —</option>
</select>
<svg class="select-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>
<div class="form-group col-full" style="margin-bottom:0">
<label class="form-label" for="h_obs">Observaciones</label>
<input class="form-input" type="text" id="h_obs" name="h_observaciones"
placeholder="Descripción…">
</div>
</div>
<button type="submit" class="btn btn-primary btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Añadir al historial
</button>
</form>
</div>
{% endif %}
</div> {# ← cierre de form-card #}
<script>
function confirmarEliminar(btn) {
if (confirm('¿Eliminar esta entrada del historial?')) {
btn.closest('form').submit();
}
}
function formatFecha(f) {
if (!f) return '';
// Si ya está en formato DD-MM-YYYY lo devuelve tal cual
if (/^\d{2}-\d{2}-\d{4}$/.test(f)) return f;
// Convertir YYYY-MM-DD a DD-MM-YYYY
var partes = f.substring(0, 10).split('-');
if (partes.length === 3) return partes[2] + '-' + partes[1] + '-' + partes[0];
return f;
}
function fila(label, valor) {
if (!valor) return '';
return `<tr>
<td style="font-weight:600;color:#555;padding:6px 12px;width:40%;border-bottom:1px solid #eee;font-size:11px;text-transform:uppercase;letter-spacing:.5px">${label}</td>
<td style="padding:6px 12px;border-bottom:1px solid #eee;font-size:13px">${valor}</td>
</tr>`;
}
function onSituacionChange(valor) {
var selectPoblacion = document.getElementById('h_poblacion');
var selectUnidad = document.getElementById('h_unidad');
var inputGrupo = document.getElementById('h_grupo');
var campSituacion = document.getElementById('situacion');
// Resetear desplegables dependientes
selectPoblacion.value = '';
selectUnidad.innerHTML = '<option value="">— Primero selecciona población —</option>';
selectUnidad.disabled = true;
if (valor === 'ACTIVO') {
// Solo ACTIVO habilita población y unidad
selectPoblacion.disabled = false;
inputGrupo.disabled = false;
}
}
function confirmarEliminarVehiculo(btn) {
if (confirm('¿Eliminar el vehículo {{ nombre_vehiculo }}? Esta acción no se puede deshacer.')) {
btn.closest('form').submit();
}
}
function cargarUnidades(poblacionId) {
var selectUnidad = document.getElementById('h_unidad');
selectUnidad.innerHTML = '<option value="">— Cargando… —</option>';
selectUnidad.disabled = true;
if (!poblacionId) {
selectUnidad.innerHTML = '<option value="">— Primero selecciona población —</option>';
return;
}
fetch('/api/unidades/' + poblacionId)
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.length === 0) {
selectUnidad.innerHTML = '<option value="">— Sin unidades disponibles —</option>';
return;
}
var opciones = '<option value="">— Selecciona unidad —</option>';
data.forEach(function(u) {
opciones += '<option value="' + u.unidad + '">' + u.unidad + '</option>';
});
selectUnidad.innerHTML = opciones;
selectUnidad.disabled = false;
})
.catch(function() {
selectUnidad.innerHTML = '<option value="">— Error al cargar —</option>';
});
}
function checkMatricula(valor) {
var bloque = document.getElementById('bloque-intervenido');
if (valor && valor.trim().toUpperCase().endsWith('Q')) {
bloque.style.display = 'block';
} else {
bloque.style.display = 'none';
}
}
</script>
<script>
function imprimirFicha() {
var v = {
id: '{{ vehiculo["id"] }}',
m_oficial: '{{ vehiculo["m_oficial"] or "" }}',
m_civil: '{{ vehiculo["m_civil"] or "" }}',
m_propia: '{{ vehiculo["m_propìa"] or "" }}',
denominacion: '{{ vehiculo["denominacion"] or "" }}',
marca: '{{ vehiculo["marca"] or "" }}',
modelo: '{{ vehiculo["modelo"] or "" }}',
tipo: '{{ vehiculo["tipo"] or "" }}',
color: '{{ vehiculo["color"] or "" }}',
VIN: '{{ vehiculo["VIN"] or "" }}',
matricula: '{{ vehiculo["numero_identificacion"] or "" }}',
potencia: '{{ vehiculo["potencia"] or "" }}',
cilindrada: '{{ vehiculo["cilindrada"] or "" }}',
combustible: '{{ vehiculo["combustible"] or "" }}',
neumaticos: '{{ vehiculo["neumaticos"] or "" }}',
plazas: '{{ vehiculo["plazas"] or "" }}',
km: '{{ vehiculo["km"] or "" }}',
km_1: '{{ vehiculo["km_1"] or "" }}',
itv_ultima: '{{ vehiculo["itv_ultima"] or "" }}',
itv: '{{ vehiculo["itv"] or "" }}',
situacion: '{{ vehiculo["situacion"] or "" }}',
poblacion: '{{ vehiculo["poblacion"] or "" }}',
unidad: '{{ vehiculo["unidad"] or "" }}',
poliza: '{{ vehiculo["poliza"] or "" }}',
viat: '{{ vehiculo["viat"] or "" }}',
};
var fecha = new Date().toLocaleDateString('es-ES');
// Construir tabla de historial
var historialRows = '';
{% if historial %}
{% for h in historial %}
historialRows += `<tr>
<td>${'{{ h["fecha"] | fecha_es }}' || '—'}</td>
<td>${'{{ h["matricula"] or "—" }}'}</td>
<td>${'{{ h["situacion"] or "—" }}'}</td>
<td>${'{{ h["poblacion"] or "—" }}'}</td>
<td>${'{{ h["unidad"] or "—" }}'}</td>
<td>${'{{ h["grupo"] or "—" }}'}</td>
<td style="color:#555">${'{{ h["observaciones"] or "—" }}'}</td>
</tr>`;
{% endfor %}
{% endif %}
var htmlHistorial = historialRows ? `
<div class="section" style="margin-top:20px">
<div class="section-title">Historial</div>
<table>
<thead>
<tr>
<th>Fecha</th>
<th>Matrícula</th>
<th>Situación</th>
<th>Población</th>
<th>Unidad</th>
<th>Grupo</th>
<th>Observaciones</th>
</tr>
</thead>
<tbody>${historialRows}</tbody>
</table>
</div>` : '';
var html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
<title>Ficha vehículo ${v.m_oficial}</title>
<style>
* { box-sizing:border-box; margin:0; padding:0; }
body { font-family: Arial, sans-serif; color:#1a1d29; padding:24px; font-size:13px; }
.header { display:flex; justify-content:space-between; align-items:flex-start;
border-bottom:3px solid #5c6ef8; padding-bottom:16px; margin-bottom:20px; }
.header h1 { font-size:20px; color:#5c6ef8; margin-bottom:4px; }
.header .meta { font-size:11px; color:#888; }
.badge { background:#5c6ef8; color:#fff; font-size:11px; font-weight:700;
padding:3px 10px; border-radius:20px; display:inline-block; margin-top:6px; }
.badge.baja { background:#ef4444; }
.badge.activo { background:#10b981; }
.section { margin-bottom:20px; }
.section-title { font-size:10px; font-weight:700; letter-spacing:1px;
text-transform:uppercase; color:#5c6ef8;
border-bottom:1px solid #5c6ef8; padding-bottom:4px;
margin-bottom:8px; }
table { width:100%; border-collapse:collapse; }
.grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
/* Tabla historial ocupa ancho completo */
.section table th {
background:#5c6ef8; color:#fff; padding:5px 8px;
text-align:left; font-size:9px; letter-spacing:.5px;
}
.section table td {
padding:5px 8px; border-bottom:1px solid #eee; font-size:10px;
}
.section table tr:nth-child(even) td { background:#f5f6fa; }
/* Filas datos vehículo */
.data-table td:first-child {
font-weight:600; color:#555; width:40%;
font-size:11px; text-transform:uppercase; letter-spacing:.5px;
}
.data-table td { padding:6px 12px; border-bottom:1px solid #eee; font-size:13px; }
.footer { margin-top:24px; font-size:10px; color:#aaa;
text-align:center; border-top:1px solid #eee; padding-top:12px; }
@media print {
body { padding:12px; }
@page { margin:1.5cm; size: A4; }
}
</style>
</head><body>
<div class="header">
<div>
<div class="meta">Ficha de vehículo · ID #${v.id}</div>
<h1>${v.m_oficial || v.denominacion || 'Sin matrícula'}</h1>
<h3 class="badge ${v.situacion === 'ALTA' ? 'alta' : v.situacion === 'BAJA' ? 'baja' : v.situacion === 'ACTIVO' ? 'activo' : ''}">
${v.situacion || 'Sin situación'}
</h3>
</div>
<div style="text-align:right">
<div class="meta">Generado el ${fecha}</div>
<div style="font-size:18px;font-weight:700;margin-top:8px">${v.marca} ${v.modelo}</div>
<div class="meta">${v.denominacion}</div>
</div>
</div>
<div class="grid">
<div>
<div class="section">
<div class="section-title">Identificación</div>
<table class="data-table">
${fila('M. oficial', v.m_oficial)}
${fila('M. civil', v.m_civil)}
${fila('M. propia', v.m_propia)}
${fila('VIN', v.VIN)}
${fila('Denominación', v.denominacion)}
</table>
</div>
<div class="section">
<div class="section-title">Características técnicas</div>
<table class="data-table">
${fila('Marca', v.marca)}
${fila('Modelo', v.modelo)}
${fila('Tipo', v.tipo)}
${fila('Color', v.color)}
${fila('Cilindrada', v.cilindrada)}
${fila('Potencia', v.potencia ? v.potencia + ' CV' : '')}
${fila('Combustible', v.combustible)}
${fila('Neumáticos', v.neumaticos)}
${fila('Plazas', v.plazas)}
</table>
</div>
</div>
<div>
<div class="section">
<div class="section-title">Situación y destino</div>
<table class="data-table">
${fila('Situación', v.situacion)}
${fila('Población', v.poblacion)}
${fila('Unidad', v.unidad)}
</table>
</div>
<div class="section">
<div class="section-title">ITV y kilometraje</div>
<table class="data-table">
${fila('Km actuales', v.km)}
${fila('Próxima ITV', formatFecha(v.itv))}
</table>
</div>
<div class="section">
<div class="section-title">Otros datos</div>
<table class="data-table">
${fila('Póliza', v.poliza)}
${fila('Via-T', v.viat)}
</table>
</div>
</div>
</div>
${htmlHistorial}
</body></html>`;
var win = window.open('', '_blank', 'width=1000,height=750');
win.document.write(html);
win.document.close();
win.focus();
setTimeout(function() { win.print(); }, 400);
}
</script>
{% endblock %}
+391
View File
@@ -0,0 +1,391 @@
{% extends "base.html" %}
{% block title %}Vehículos{% endblock %}
{% block content %}
<div class="page-header">
<div class="page-eyebrow">Base de datos</div>
<div class="page-title">Vehículos</div>
<div class="page-subtitle">Gestión del parque móvil. Búsqueda por m.oficial, m.civil, VIN, marca, modelo, poblacion, unidad, denominación, tipo, operacion, situacion.</div>
</div>
<div class="crud-toolbar">
<form method="GET" action="{{ url_for('vehiculos_list') }}" class="search-wrap" id="searchForm">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input
type="text"
name="q"
id="searchInput"
placeholder="Buscar… palabras separadas por comas: Ford, Transit"
value="{{ q }}"
autocomplete="off"
autofocus
style="width: 400px;"
>
<button type="button" id="clearBtn" title="Limpiar búsqueda"
style="display:{{ 'flex' if q else 'none' }};position:absolute;right:-25px;top:50%;
transform:translateY(-50%);background:none;border:none;cursor:pointer;
color:var(--muted);padding:4px;align-items:center;transition:color .15s;
z-index:2;"
onmouseover="this.style.color='var(--text)'"
onmouseout="this.style.color='var(--muted)'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
style="pointer-events:none;display:block;">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</form>
{# Casilla incluir bajas #}
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;
font-size:13px;color:var(--muted);white-space:nowrap;
padding:8px 12px;border:1px solid var(--border);border-radius:8px;
transition:border-color .15s,color .15s;user-select:none; margin-left: 50px;"
onmouseover="this.style.borderColor='var(--accent)';this.style.color='var(--text)'"
onmouseout="this.style.borderColor='var(--border)';this.style.color='var(--muted)'">
<input type="checkbox" id="inclBajas" name="incl_bajas"
style="width:15px;height:15px;accent-color:var(--accent);cursor:pointer;"
{% if incl_bajas %}checked{% endif %}>
Incluir bajas
</label>
<div style="display:flex;gap:8px;align-items:center;margin-left:auto;">
<a href="{{ url_for('vehiculos_exportar', q=q) }}" class="btn btn-ghost" id="btnExcel" title="Exportar a Excel">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="12" y1="18" x2="12" y2="12"/>
<line x1="9" y1="15" x2="12" y2="18"/><line x1="15" y1="15" x2="12" y2="18"/>
</svg>
Excel
</a>
<button type="button" class="btn btn-ghost" id="btnPDF" title="Imprimir / Guardar PDF">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 6 2 18 2 18 9"/>
<path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/>
<rect x="6" y="14" width="12" height="8"/>
</svg>
Imprimir - PDF
</button>
<button type="button" class="btn btn-ghost" id="btnCopiar" title="Copiar al portapapeles">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
</svg>
Copiar
</button>
</div>
<a href="{{ url_for('vehiculos_nuevo') }}" class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Nuevo vehículo
</a>
</div>
{# Sin {% if rows %} - siempre visible #}
<p id="counter" style="margin-bottom:10px;margin-top:1px;font-size:12px;color:var(--muted)">
{{ rows|length }} registro{{ 's' if rows|length != 1 }}{% if q %} — filtrado por «{{ q }}»{% endif %}
</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<!-- <th hidden>ID</th> -->
<th>M. Oficial</th>
<th>M. Civil</th>
<th>Marca</th>
<th>Modelo</th>
<th>Poblacion</th>
<th>Unidad</th>
<!-- <th>Grupo</th> -->
<th>Tipo vehículo</th>
<th>Situación</th>
</tr>
</thead>
<tbody id="tbody">
{% include 'vehiculos/rows.html' %}
</tbody>
</table>
</div>
<script>
var input = document.getElementById('searchInput');
var tbody = document.getElementById('tbody');
var counter = document.getElementById('counter');
var clearBtn = document.getElementById('clearBtn');
var timer;
// Inicializar contador al cargar la página
(function() {
var filas = document.querySelectorAll('#tbody tr[data-id]').length;
var q = input.value.trim();
if (counter) {
counter.textContent = filas + ' registro' + (filas !== 1 ? 's' : '')
+ (q ? ' — filtrado por «' + q + '»' : '');
}
})();
// Búsqueda con debounce
input.addEventListener('input', function() {
clearBtn.style.display = this.value ? 'flex' : 'none';
clearTimeout(timer);
timer = setTimeout(function() { buscar(input.value.trim()); }, 300);
});
clearBtn.addEventListener('click', function() {
input.value = '';
clearBtn.style.display = 'none';
input.focus();
buscar('');
});
var inclBajas = document.getElementById('inclBajas');
// Al cambiar el checkbox, relanzar búsqueda
inclBajas.addEventListener('change', function() {
buscar(input.value.trim());
});
function buscar(q) {
var bajas = inclBajas.checked ? '1' : '0';
var btnExcel = document.getElementById('btnExcel');
if (btnExcel) {
btnExcel.href = '{{ url_for("vehiculos_exportar") }}?q=' + encodeURIComponent(q) + '&incl_bajas=' + bajas;;
}
var url = '{{ url_for("vehiculos_list") }}?q='
+ encodeURIComponent(q) + '&solo_filas=1&incl_bajas=' + bajas;
fetch(url)
.then(function(r) {
// Si el servidor devuelve 302 o la URL cambió, la sesión expiró
if (r.redirected || r.url.includes('/login')) {
window.location = '{{ url_for("login") }}';
return null;
}
return r.text();
})
.then(function(html) {
if (!html) return;
tbody.innerHTML = html;
var filas = document.querySelectorAll('#tbody tr[data-id]').length;
if (counter) {
counter.textContent = filas + ' registro' + (filas !== 1 ? 's' : '')
+ (q ? ' — filtrado por «' + q + '»' : '');
counter.style.display = '';
}
})
.catch(function() {
window.location = '{{ url_for("login") }}';
});
}
/* ── PDF ── */
document.getElementById('btnPDF').addEventListener('click', function() {
var q = input.value.trim();
var titulo = 'Listado de vehículos' + (q ? ' — Filtro: ' + q : '');
var fecha = new Date().toLocaleDateString('es-ES', {day:'2-digit', month:'2-digit', year:'numeric'});
// Construir tabla con los datos visibles
var heads = [];
document.querySelectorAll('thead th').forEach(function(th, i) {
if (i < 9) heads.push(th.innerText.trim());
});
var filas = '';
tbody.querySelectorAll('tr[data-id]').forEach(function(tr) {
var celdas = tr.querySelectorAll('td');
filas += '<tr>';
for (var i = 0; i < 10; i++) {
filas += '<td>' + (celdas[i] ? celdas[i].innerText.trim() : '') + '</td>';
}
filas += '</tr>';
});
var html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
<title>${titulo}</title>
<style>
body { font-family: Arial, sans-serif; font-size: 11px; color: #1a1d29; margin: 24px; }
h2 { font-size: 15px; margin-bottom: 4px; }
.meta { font-size: 10px; color: #666; margin-bottom: 16px; }
table { width: 100%; border-collapse: collapse; }
th { background: #5c6ef8; color: #fff; padding: 7px 8px; text-align: left; font-size: 10px; letter-spacing: .5px; }
td { padding: 6px 8px; border-bottom: 1px solid #e3e6ef; font-size: 10px; }
tr:nth-child(even) td { background: #eef0f8; }
.badge { background: #eef0f8; color: #5c6ef8; padding: 2px 7px; border-radius: 20px;
border: 1px solid rgba(92,110,248,.3); font-size: 9px; font-weight: 700; }
@media print { body { margin: 12px; } }
</style></head><body>
<h2>${titulo}</h2>
<div class="meta">Generado el ${fecha} · ${tbody.querySelectorAll('tr[data-id]').length} registros</div>
<table>
<thead><tr>${heads.map(function(h){ return '<th>'+h+'</th>'; }).join('')}</tr></thead>
<tbody>${filas}</tbody>
</table>
</body></html>`;
var win = window.open('', '_blank', 'width=900,height=650');
win.document.write(html);
win.document.close();
win.focus();
win.print();
win.close();
});
/* ── COPIAR ── */
document.getElementById('btnCopiar').addEventListener('click', function() {
var btn = this;
// Leer filas EN EL MOMENTO del click, no antes
var heads = ['M. Oficial','M. Civil','Marca','Modelo','Poblacion','Unidad','Grupo','Denominación','Situación'];
var lineas = [heads.join('\t')];
document.querySelectorAll('#tbody tr[data-id]').forEach(function(tr) {
var fila = [];
tr.querySelectorAll('td').forEach(function(td) {
fila.push(td.innerText.trim());
});
lineas.push(fila.join('\t'));
});
var texto = lineas.join('\n');
if (!texto.trim() || lineas.length <= 1) {
alert('No hay datos para copiar.');
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(texto)
.then(function() { mostrarCopiado(btn); })
.catch(function() { copiarFallback(texto, btn); });
} else {
copiarFallback(texto, btn);
}
});
function copiarFallback(texto, btn) {
var ta = document.createElement('textarea');
ta.value = texto;
ta.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;';
document.body.appendChild(ta);
ta.focus();
ta.select();
ta.setSelectionRange(0, 99999); // Para móviles
var ok = false;
try {
ok = document.execCommand('copy');
} catch(e) { ok = false; }
document.body.removeChild(ta);
if (ok) {
mostrarCopiado(btn);
} else {
alert('No se pudo copiar automáticamente.\nSelecciona el texto manualmente con Ctrl+A y Ctrl+C.');
}
}
function mostrarCopiado(btn) {
var orig = btn.innerHTML;
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg> Copiado';
btn.style.color = 'var(--success)';
btn.style.borderColor = 'var(--success)';
setTimeout(function() {
btn.innerHTML = orig;
btn.style.color = '';
btn.style.borderColor = '';
}, 2000);
}
</script>
<script>
/* ── ORDENACIÓN POR COLUMNAS ── */
(function() {
var estado = { col: -1, asc: true };
document.querySelectorAll('thead th').forEach(function(th, idx) {
// No ordenar la última columna (acciones/estado)
th.style.cursor = 'pointer';
th.style.userSelect = 'none';
th.title = 'Pulsa para ordenar';
// Añadir icono de orden
var icon = document.createElement('span');
icon.style.cssText = 'margin-left:5px;opacity:.3;font-size:10px;';
icon.textContent = '⇅';
icon.className = 'sort-icon';
th.appendChild(icon);
th.addEventListener('click', function() {
var tbody = document.querySelector('tbody');
var filas = Array.from(tbody.querySelectorAll('tr[data-id], tr[onclick]'));
if (!filas.length) return;
var mismaCol = estado.col === idx;
estado.asc = mismaCol ? !estado.asc : true;
estado.col = idx;
// Actualizar iconos
document.querySelectorAll('.sort-icon').forEach(function(ic) {
ic.textContent = '⇅';
ic.style.opacity = '.3';
});
icon.textContent = estado.asc ? '↑' : '↓';
icon.style.opacity = '1';
icon.style.color = 'var(--accent)';
filas.sort(function(a, b) {
var celdaA = a.querySelectorAll('td')[idx];
var celdaB = b.querySelectorAll('td')[idx];
if (!celdaA || !celdaB) return 0;
var valA = celdaA.innerText.trim().toLowerCase();
var valB = celdaB.innerText.trim().toLowerCase();
// Detectar si es número
var numA = parseFloat(valA.replace(/[^0-9.-]/g, ''));
var numB = parseFloat(valB.replace(/[^0-9.-]/g, ''));
var esNumero = !isNaN(numA) && !isNaN(numB);
// Detectar si es fecha (formato YYYY-MM-DD o DD-MM-YYYY)
var fechaRegex = /^\d{2}[-\/]\d{2}[-\/]\d{4}$|^\d{4}[-\/]\d{2}[-\/]\d{2}$/;
var esFecha = fechaRegex.test(celdaA.innerText.trim());
var resultado;
if (esNumero) {
resultado = numA - numB;
} else if (esFecha) {
// Normalizar DD-MM-YYYY a YYYY-MM-DD para comparar
var normA = valA.replace(/(\d{2})-(\d{2})-(\d{4})/, '$3-$2-$1');
var normB = valB.replace(/(\d{2})-(\d{2})-(\d{4})/, '$3-$2-$1');
resultado = normA.localeCompare(normB);
} else {
resultado = valA.localeCompare(valB, 'es');
}
return estado.asc ? resultado : -resultado;
});
// Reinsertar filas ordenadas
filas.forEach(function(fila) { tbody.appendChild(fila); });
});
});
})();
</script>
{% endblock %}
+108
View File
@@ -0,0 +1,108 @@
{% if rows %}
{% for r in rows %}
<tr data-id="{{ r['id'] }}"
onclick="window.location='/vehiculos/{{ r['id'] }}/editar?q={{ q|urlencode }}'"
style="cursor:pointer;"
onmouseover="this.style.background='var(--pill-bg)'"
onmouseout="this.style.background=''">
<!-- <td hidden class="td-id">#{{ r['id'] }}</td> -->
<td>{{ r['m_oficial'] or '—' }}</td>
<td>{{ r['m_civil'] or '—' }}</td>
<!-- <td>{{ r['vin'] or '—' }}</td> -->
<td><strong>{{ r['marca'] or '—' }}</strong></td>
<td>{{ r['modelo'] or '—' }}</td>
<td>{{ r['poblacion'] or '—' }}</td>
<td>{{ r['unidad'] or '—' }}</td>
<!-- <td>{{ r['grupo'] or '—' }}</td> -->
<td>{{ r['tipo'] or '—' }}</td>
<td>
{% if r['situacion'] == 'BAJA' %}
<span style="background:rgba(248,113,113,.12);color:#f87171;font-size:11px;font-weight:600;padding:3px 9px;border-radius:20px;border:1px solid rgba(248,113,113,.3)">
BAJA
</span>
{% elif r['situacion'] %}
<span style="background:rgba(52,211,153,.12);color:#34d399;font-size:11px;font-weight:600;padding:3px 9px;border-radius:20px;border:1px solid rgba(52,211,153,.3)">
{{ r['situacion'] }}
</span>
{% else %}—{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="10" rowspan="7">
<div class="empty-state" style="color: rgb(202, 145, 145);">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M5 17H3a2 2 0 01-2-2V9a2 2 0 012-2h11l4 4v4a2 2 0 01-2 2h-1"/>
<circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>
</svg>
<br>
{% if q %}
No se encontraron resultados para <strong>"{{ q }}"</strong>.
{% else %}
<!-- No hay vehículos registrados aún. -->
{% endif %}
</div>
</td>
</tr>
{% endif %}
<!-- {% if rows %}
{% for r in rows %}
<tr data-id="{{ r['id'] }}">
<td class="td-id">#{{ r['id'] }}</td>
<td>{{ r['m_oficial'] or '—' }}</td>
<td>{{ r['m_civil'] or '—' }}</td>
<td><strong>{{ r['marca'] or '—' }}</strong></td>
<td>{{ r['modelo'] or '—' }}</td>
<td>{{ r['tipo'] or '—' }}</td>
<td>
{% if r['tipo'] %}
<span style="background:rgba(92,110,248,.12);color:var(--accent);font-size:11px;font-weight:600;padding:3px 9px;border-radius:20px;border:1px solid rgba(92,110,248,.25)">
{{ r['tipo'] }}
</span>
{% else %}—{% endif %}
</td>
<td>
{% if r['situacion'] == 'BAJA' %}
<span style="background:rgba(248,113,113,.12);color:#f87171;font-size:11px;font-weight:600;padding:3px 9px;border-radius:20px;border:1px solid rgba(248,113,113,.3)">
BAJA
</span>
{% elif r['situacion'] %}
<span style="background:rgba(52,211,153,.12);color:#34d399;font-size:11px;font-weight:600;padding:3px 9px;border-radius:20px;border:1px solid rgba(52,211,153,.3)">
{{ r['situacion'] }}
</span>
{% else %}—{% endif %}
</td>
<td>
<div class="td-actions" style="justify-content:flex-end">
<a href="/vehiculos/{{ r['id'] }}/editar?q={{ q|urlencode }}" class="btn btn-ghost btn-sm">Editar</a>
</div>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="8">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M5 17H3a2 2 0 01-2-2V9a2 2 0 012-2h11l4 4v4a2 2 0 01-2 2h-1"/>
<circle cx="7" cy="17" r="2"/><circle cx="17" cy="17" r="2"/>
</svg>
{% if q %}
No se encontraron resultados para <strong>"{{ q }}"</strong>.
{% else %}
No hay vehículos registrados aún.
{% endif %}
</div>
</td>
</tr>
{% endif %} -->