correcciones finales

This commit is contained in:
2026-09-11 23:30:29 +02:00
parent 5d2912f789
commit 2b8f5374c9
18 changed files with 1582 additions and 440 deletions
Vendored
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+47 -105
View File
@@ -16,7 +16,8 @@ app.secret_key = 'clave-secreta-navbar-app'
# Límite máximo de subida: 5 MiB
# app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
DATABASE = 'database/pyfleet.db'
# DATABASE = 'database/pyfleet.db'
DATABASE = 'database/auto.db'
PASSWORD_HASH = generate_password_hash('123')
@@ -236,7 +237,7 @@ def vehiculos_list():
NORM(modelo) LIKE ? OR
NORM(poblacion) LIKE ? OR
NORM(unidad) LIKE ? OR
NORM(denominacion) LIKE ? OR
NORM(tipo) LIKE ? OR
NORM(situacion) LIKE ?
)''')
params.extend([like] * 9)
@@ -245,7 +246,7 @@ def vehiculos_list():
rows = db.execute(
f'''SELECT id, m_oficial, m_reservada, marca, modelo, poblacion, unidad,
denominacion, tipo, situacion, lanzadestellos
tipo, situacion, lanzadestellos
FROM vehiculos
WHERE {where} {filtro_bajas}
ORDER BY id''',
@@ -409,12 +410,20 @@ def vehiculos_eliminar(id):
return redirect(url_for('vehiculos_list', q=q) if q else url_for('vehiculos_list'))
@app.route('/vehiculos/<int:vid>/historial/nuevo', methods=['POST'])
@login_required
def historial_nuevo(vid):
q = request.form.get('_q', '')
db = get_db()
# Recoger TODOS los parámetros de navegación del POST
q = request.form.get('_q', '')
desde = request.form.get('_desde', '')
hasta = request.form.get('_hasta', '')
q_itv = request.form.get('_q_itv', '')
origen = request.form.get('_origen', '')
q_viat = request.form.get('_q_viat', '') # ← POST no GET
# Datos del historial
h_fecha = request.form.get('h_fecha') or None
h_vehiculo_id = request.form.get('h_vehiculo_id', '').strip()
h_matricula = request.form.get('h_matricula', '').strip()
@@ -424,11 +433,7 @@ def historial_nuevo(vid):
h_grupo = request.form.get('h_grupo', '').strip()
h_observaciones = request.form.get('h_observaciones', '').strip()
desde = request.form.get('_desde', '')
hasta = request.form.get('_hasta', '')
q_itv = request.form.get('_q_itv', '')
# Obtener el NOMBRE de la población a partir del id
# Obtener nombre de población
poblacion_nombre = ''
if h_poblacion_id:
row = db.execute('SELECT poblacion FROM poblaciones WHERE id=?', (h_poblacion_id,)).fetchone()
@@ -440,32 +445,47 @@ def historial_nuevo(vid):
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (h_fecha, h_vehiculo_id, h_matricula, h_situacion, poblacion_nombre, h_unidad, h_grupo, h_observaciones))
# Si la unidad es BAJA, actualizar situacion del vehiculo
# Actualizar situacion del vehiculo
if h_situacion.upper() == 'BAJA':
db.execute("UPDATE vehiculos SET situacion='BAJA' WHERE id=?", (vid,))
db.execute("UPDATE vehiculos SET situacion='BAJA', poblacion='', unidad='' WHERE id=?", (vid,))
else:
db.execute("UPDATE vehiculos SET situacion=?, poblacion=?, unidad=?, grupo=? WHERE id=?", (h_situacion, poblacion_nombre, h_unidad, h_grupo, vid,))
db.execute(
"UPDATE vehiculos SET situacion=?, poblacion=?, unidad=?, grupo=? WHERE id=?",
(h_situacion, poblacion_nombre, h_unidad, h_grupo, vid)
)
db.commit()
flash('Entrada añadida al historial.', 'ok')
# Volver a ITV si venía de ahí, si no al formulario del vehículo
# Redirect según origen
if origen == 'viat':
return redirect(f'/vehiculos/{vid}/editar?origen=viat&q_viat={q_viat}')
if desde or hasta or q_itv:
return redirect(f'/vehiculos/{vid}/editar?desde={desde}&hasta={hasta}&q_itv={q_itv}')
# return redirect(f'/vehiculos/{vid}/editar?q={q}')
return redirect(url_for('vehiculos_editar', id=vid, q=q))
@app.route('/vehiculos/<int:vid>/historial/<int:hid>/eliminar', methods=['POST'])
@login_required
def historial_eliminar(vid, hid):
q = request.form.get('_q', '')
desde = request.form.get('_desde', '')
hasta = request.form.get('_hasta', '')
unidad_itv = request.form.get('_unidad_itv', '')
db = get_db()
# Recoger parámetros de navegación
q = request.form.get('_q', '')
desde = request.form.get('_desde', '')
hasta = request.form.get('_hasta', '')
q_itv = request.form.get('_q_itv', '')
origen = request.form.get('_origen', '')
q_viat = request.form.get('_q_viat', '') # ← POST no GET
# 1. Comprobar que el vehículo existe
vehiculo = db.execute('''
SELECT id, m_oficial
@@ -541,15 +561,15 @@ def historial_eliminar(vid, hid):
WHERE id = ?
''', (vid,))
# 7. Confirmar cambios
db.commit()
flash('Entrada eliminada del historial.', 'ok')
# Volver a ITV si venía de ahí
if desde or hasta or unidad_itv:
return redirect(f'/vehiculos/{vid}/editar?desde={desde}&hasta={hasta}&unidad_itv={unidad_itv}')
# return redirect(f'/vehiculos/{vid}/editar?q={q}')
# Redirect según origen
if origen == 'viat':
return redirect(f'/vehiculos/{vid}/editar?origen=viat&q_viat={q_viat}')
if desde or hasta or q_itv:
return redirect(f'/vehiculos/{vid}/editar?desde={desde}&hasta={hasta}&q_itv={q_itv}')
url = f'/vehiculos/{vid}/editar'
if q:
@@ -625,7 +645,7 @@ def vehiculos_exportar():
ws = wb.active
ws.title = 'Vehículos'
headers = ['M. Oficial', 'M. Civil', 'Marca', 'Modelo',
headers = ['M. Oficial', 'M. Reservada', 'Marca', 'Modelo',
'Poblacion', 'Unidad', 'Grupo', 'Denominación', 'Situación' ]
header_fill = PatternFill('solid', fgColor='5C6EF8')
header_font = Font(bold=True, color='FFFFFF', size=11)
@@ -684,7 +704,8 @@ def documentos_upload(id):
q_itv = request.form.get('_q_itv', '')
origen = request.form.get('_origen', '')
q_viat = request.form.get('_q_viat', '')
nombre = request.form.get('nombre_doc', '').strip()
nombre = request.form.get('nombre_doc', '').strip() + '.pdf'
if 'pdf' not in request.files:
flash('No se seleccionó ningún archivo.', 'error')
@@ -757,7 +778,7 @@ def documentos_descargar(doc_id):
return send_from_directory(UPLOAD_FOLDER, doc['filename'],
as_attachment=True,
download_name=doc['nombre'])
# download_name=doc['nombre'] + '.pdf')
# download_name=doc['nombre'] + '.pdf'
@app.route('/vehiculos/documentos/<int:doc_id>/eliminar', methods=['POST'])
@login_required
@@ -781,85 +802,6 @@ def documentos_eliminar(doc_id):
# @app.route('/vehiculos/<int:id>/upload', methods=['POST'])
# @login_required
# def vehiculos_upload(id):
# q = request.form.get('_q', '')
# incl_bajas = request.form.get('_incl_bajas', '0')
# desde = request.form.get('_desde', '')
# hasta = request.form.get('_hasta', '')
# q_itv = request.form.get('_q_itv', '')
# origen = request.form.get('_origen', '')
# q_viat = request.form.get('_q_viat', '')
# if 'pdf' not in request.files:
# flash('No se seleccionó ningún archivo.', 'error')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# file = request.files['pdf']
# if file.filename == '':
# flash('No se seleccionó ningún archivo.', 'error')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# if file and allowed_file(file.filename):
# # Guardar con nombre único basado en id del vehículo
# os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# filename = f'ficha_tecnica_id_{id}.pdf'
# filepath = os.path.join(UPLOAD_FOLDER, filename)
# file.save(filepath)
# # Guardar referencia en BD
# db = get_db()
# db.execute('UPDATE vehiculos SET pdf=? WHERE id=?', (filename, id))
# db.commit()
# flash('Documento PDF subido correctamente.', 'ok')
# else:
# flash('Solo se permiten archivos PDF.', 'error')
# url = f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}'
# if origen == 'viat':
# url = f'/vehiculos/{id}/editar?origen=viat&q_viat={q_viat}'
# elif desde or hasta or q_itv:
# url = f'/vehiculos/{id}/editar?desde={desde}&hasta={hasta}&q_itv={q_itv}'
# return redirect(url)
# @app.route('/vehiculos/<int:id>/pdf')
# @login_required
# def vehiculos_pdf(id):
# from flask import send_from_directory
# db = get_db()
# vehiculo = db.execute('SELECT pdf FROM vehiculos WHERE id=?', (id,)).fetchone()
# if not vehiculo or not vehiculo['pdf']:
# flash('No hay documento PDF para este vehículo.', 'error')
# return redirect(f'/vehiculos/{id}/editar')
# return send_from_directory(UPLOAD_FOLDER, vehiculo['pdf'], as_attachment=False)
# @app.route('/vehiculos/<int:id>/pdf/eliminar', methods=['POST'])
# @login_required
# def vehiculos_pdf_eliminar(id):
# q = request.form.get('_q', '')
# incl_bajas = request.form.get('_incl_bajas', '0')
# db = get_db()
# vehiculo = db.execute('SELECT pdf FROM vehiculos WHERE id=?', (id,)).fetchone()
# if vehiculo and vehiculo['pdf']:
# filepath = os.path.join(UPLOAD_FOLDER, vehiculo['pdf'])
# if os.path.exists(filepath):
# os.remove(filepath)
# db.execute('UPDATE vehiculos SET pdf=NULL WHERE id=?', (id,))
# db.commit()
# flash('Documento PDF eliminado.', 'ok')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# Ruta API que devuelve unidades según población
@app.route('/api/unidades/<int:poblacion_id>')
@login_required
@@ -978,7 +920,7 @@ def itv_exportar():
ws = wb.active
ws.title = 'ITV'
headers = ['M. Oficial', 'M. Civil', 'Marca', 'Modelo',
headers = ['M. Oficial', 'M. Reservada', 'Marca', 'Modelo',
'Denominación', 'Tipo', 'Unidad', 'Población', 'Fecha ITV']
header_fill = PatternFill('solid', fgColor='5C6EF8')
header_font = Font(bold=True, color='FFFFFF', size=11)
@@ -1432,7 +1374,7 @@ def viat_exportar():
ws = wb.active
ws.title = 'Via-T'
headers = ['Via-T', 'M. Oficial', 'M. Civil', 'Marca',
headers = ['Via-T', 'M. Oficial', 'M. Reservada', 'Marca',
'Modelo', 'Población', 'Unidad', 'Tipo']
header_fill = PatternFill('solid', fgColor='5C6EF8')
header_font = Font(bold=True, color='FFFFFF', size=11)
BIN
View File
Binary file not shown.
Binary file not shown.
+8 -2
View File
@@ -238,8 +238,14 @@ CREATE TABLE "unidades" (
)
Actualización vial
UPDATE vehiculos
SET viat = substr(
notas,
instr(lower(notas), 'viat '),
24
)
WHERE instr(lower(notas), 'viat ') > 0;
----------------------------------------
CONSULTAR TIPOS DE VEHICULO
+247
View File
@@ -0,0 +1,247 @@
DROP TABLE IF EXISTS ACCIDENTES;
DROP TABLE IF EXISTS "CAMBIOS DE ACEITE";
DROP TABLE IF EXISTS CONTRATO;
DROP TABLE IF EXISTS "DEFINICIÓN DE TIPOS";
DROP TABLE IF EXISTS Denominación;
DROP TABLE IF EXISTS "Errores de pegado";
DROP TABLE IF EXISTS "Estado Seguro";
DROP TABLE IF EXISTS FACTURAS;
DROP TABLE IF EXISTS FUNCIONARIOS;
DROP TABLE IF EXISTS "Grupo Asignado";
DROP TABLE IF EXISTS HERRAMIENTAS;
DROP TABLE IF EXISTS "Historico destino";
DROP TABLE IF EXISTS Hoja1;
DROP TABLE IF EXISTS ITV;
DROP TABLE IF EXISTS Incidencias;
DROP TABLE IF EXISTS KILOMETROS;
DROP TABLE IF EXISTS LIQUIDACION;
DROP TABLE IF EXISTS MAQUINARIA;
DROP TABLE IF EXISTS Marcas;
DROP TABLE IF EXISTS Matriculas;
DROP TABLE IF EXISTS Modelo;
DROP TABLE IF EXISTS Multas;
DROP TABLE IF EXISTS Neumaticos;
DROP TABLE IF EXISTS Piezas;
DROP TABLE IF EXISTS POBLACIONES;
DROP TABLE IF EXISTS PROVEEDORES;
DROP TABLE IF EXISTS Peticiones;
DROP TABLE IF EXISTS Plantilla;
DROP TABLE IF EXISTS RECIBO;
DROP TABLE IF EXISTS REPARACIONES;
DROP TABLE IF EXISTS REPUESTOS;
DROP TABLE IF EXISTS "Situación Vehículo";
DROP TABLE IF EXISTS "T_RESUMEN VEHICULOS COMISARIA PROVINCIAL";
DROP TABLE IF EXISTS "T_RESUMEN VEHICULOS COMISARIAS LOCALES";
DROP TABLE IF EXISTS Tipo;
DROP TABLE IF EXISTS Unidades;
DROP TABLE IF EXISTS VACACIONES;
DROP TABLE IF EXISTS "VEHICULOS 10-01-10";
DROP TABLE IF EXISTS "VEHICULOS 28-04-10";
DROP TABLE IF EXISTS VEHICULOS_PTE;
ALTER TABLE VEHICULOS RENAME TO "vehiculos_tmp";
ALTER TABLE "vehiculos_tmp" RENAME TO vehiculos;
ALTER TABLE vehiculos DROP COLUMN "Número de Identificación";
ALTER TABLE vehiculos DROP COLUMN "Nº Cilindros/Cilindrada";
ALTER TABLE vehiculos DROP COLUMN Potencia;
ALTER TABLE vehiculos DROP COLUMN "Medidas neumaticos";
ALTER TABLE vehiculos DROP COLUMN "Fecha Destino/Situación";
ALTER TABLE vehiculos DROP COLUMN Seguro;
ALTER TABLE vehiculos DROP COLUMN Denominación;
ALTER TABLE vehiculos DROP COLUMN Grupo;
ALTER TABLE vehiculos DROP COLUMN Foto_asignada;
ALTER TABLE vehiculos RENAME COLUMN "Matrícula Oficial (DGP)" TO m_oficial;
ALTER TABLE vehiculos RENAME COLUMN "Nº ficha" TO legajo;
ALTER TABLE vehiculos RENAME COLUMN "Matrícula Civil" TO m_reservada;
ALTER TABLE vehiculos RENAME COLUMN PMM TO m_propia;
ALTER TABLE vehiculos RENAME COLUMN Población TO poblacion;
ALTER TABLE vehiculos RENAME COLUMN Marca TO marca;
ALTER TABLE vehiculos RENAME COLUMN Modelo TO modelo;
ALTER TABLE vehiculos RENAME COLUMN Tipo TO tipo;
ALTER TABLE vehiculos RENAME COLUMN Color TO color;
ALTER TABLE vehiculos RENAME COLUMN "Número de bastidor" TO VIN;
ALTER TABLE vehiculos RENAME COLUMN "Fecha de Alta" TO f_alta;
ALTER TABLE vehiculos RENAME COLUMN "Fecha de Baja" TO f_baja;
ALTER TABLE vehiculos RENAME COLUMN "Situación veh" TO situacion;
ALTER TABLE vehiculos RENAME COLUMN Kilometros TO km;
ALTER TABLE vehiculos RENAME COLUMN "Kilometros a dia 1" TO km_1;
ALTER TABLE vehiculos RENAME COLUMN "Tipo de Combustible" TO combustible;
ALTER TABLE vehiculos RENAME COLUMN "Número de plazas" TO plazas;
ALTER TABLE vehiculos RENAME COLUMN "Ultima ITV" TO itv_ultima;
ALTER TABLE vehiculos RENAME COLUMN "Tipo ITV" TO itv;
ALTER TABLE vehiculos RENAME COLUMN Departamento TO unidad;
ALTER TABLE vehiculos RENAME COLUMN "Número de Poliza" TO poliza;
ALTER TABLE vehiculos ADD COLUMN id INTEGER;
ALTER TABLE vehiculos ADD COLUMN lanzadestellos INTEGER;
ALTER TABLE vehiculos ADD COLUMN grupo TEXT;
ALTER TABLE vehiculos ADD COLUMN viat TEXT;
ALTER TABLE vehiculos ADD COLUMN int_operacion TEXT;
ALTER TABLE vehiculos ADD COLUMN int_grupo TEXT;
ALTER TABLE vehiculos ADD COLUMN int_juzgado TEXT;
ALTER TABLE vehiculos ADD COLUMN int_procedimiento TEXT;
ALTER TABLE vehiculos ADD COLUMN notas TEXT;
UPDATE vehiculos SET notas = TRIM(COALESCE(observaciones, '') || ' ' || COALESCE(historial, ''));
ALTER TABLE vehiculos DROP COLUMN observaciones;
ALTER TABLE vehiculos DROP COLUMN historial;
UPDATE vehiculos
SET lanzadestellos = 1
WHERE notas LIKE '%SAMCO%';
UPDATE vehiculos SET f_alta = substr(f_alta, 1, 10);
UPDATE vehiculos SET f_baja = substr(f_baja, 1, 10);
UPDATE vehiculos
SET situacion = CASE
WHEN f_baja IS NULL OR f_baja = '' THEN 'ACTIVO'
ELSE 'BAJA'
END;
UPDATE vehiculos
SET viat = substr(
notas,
instr(lower(notas), 'viat '),
24
)
WHERE instr(lower(notas), 'viat ') > 0;
UPDATE vehiculos SET unidad = 'BPEF' WHERE unidad = 'B.P.E. y F.';
UPDATE vehiculos SET unidad = 'BPI' WHERE unidad = 'B.P.I.';
UPDATE vehiculos SET unidad = 'BPPC' WHERE unidad = 'B.P.P.C.';
UPDATE vehiculos SET unidad = 'BPSC' WHERE unidad = 'B.P.S.C.';
UPDATE vehiculos SET unidad = 'BPSC' WHERE unidad = 'Seg. Ciudadana' AND poblacion = 'MALAGA';
UPDATE vehiculos SET unidad = 'BLSC' WHERE unidad = 'Seg. Ciudadana' AND poblacion != 'MALAGA';
UPDATE vehiculos SET unidad = 'BPSC' WHERE unidad = 'SEG. CIUDADANA';
UPDATE vehiculos SET unidad = 'BPSC' WHERE unidad = 'B.P.S.C. K';
UPDATE vehiculos SET unidad = 'DISTRITO CENTRO' WHERE unidad = 'DTO. CENTRO';
UPDATE vehiculos SET unidad = 'DISTRITO ESTE' WHERE unidad = 'DTO. ESTE';
UPDATE vehiculos SET unidad = 'DISTRITO NORTE' WHERE unidad = 'DTO. NORTE';
UPDATE vehiculos SET unidad = 'DISTRITO OESTE' WHERE unidad = 'DTO. OESTE';
UPDATE vehiculos SET unidad = 'UCOP - GOES' WHERE unidad = 'G.O.E.S.';
UPDATE vehiculos SET unidad = 'UDEF - IPLA' WHERE unidad = 'I.P.L.A.';
UPDATE vehiculos SET unidad = 'SECRETARIA GENERAL - NUCLEO RESERVA' WHERE unidad = 'NUCLEO DE RESERVA';
UPDATE vehiculos SET unidad = 'SECRETARIA GENERAL' WHERE unidad = 'SECRETARIA G.';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'SUSTITUCION';
UPDATE vehiculos SET unidad = 'AUTOMOCION' WHERE unidad = 'TALLER AUTOMOCION';
UPDATE vehiculos SET unidad = 'UCOP - GABINETE TECNICO' WHERE unidad = 'U.C.O.P. - GABINETE TÉCNICO';
UPDATE vehiculos SET unidad = 'UCOP - GOTHAM' WHERE unidad = 'U.C.O.P. - GOTHAM';
UPDATE vehiculos SET unidad = 'UCOP - HELICOPTEROS' WHERE unidad = 'U.C.O.P. - HELICOPTEROS';
UPDATE vehiculos SET unidad = 'UCOP' WHERE unidad = 'U.C.O.P. - JEFES';
UPDATE vehiculos SET unidad = 'UCOP - OMEGA' WHERE unidad = 'U.C.O.P. - OMEGA';
UPDATE vehiculos SET unidad = 'UCOP - SISTEMAS ESPECIALES' WHERE unidad = 'U.C.O.P. - SISTEMAS';
UPDATE vehiculos SET unidad = 'UCOP - UVAO' WHERE unidad = 'U.C.O.P. - UVAO';
UPDATE vehiculos SET unidad = 'UDEF' WHERE unidad = 'U.D.E.F.';
UPDATE vehiculos SET unidad = 'BPPJ - UDEV ' WHERE unidad = 'U.D.E.V.' AND poblacion = 'MALAGA';
UPDATE vehiculos SET unidad = 'BLPJ - UDEV' WHERE unidad = 'U.D.E.V.' AND poblacion != 'MALAGA';
UPDATE vehiculos SET unidad = 'UIP' WHERE unidad = 'U.I.P.';
UPDATE vehiculos SET unidad = 'BPSC - UPR' WHERE unidad = 'U.P.R.' AND poblacion = 'MALAGA';
UPDATE vehiculos SET unidad = 'BLSC - UPR' WHERE unidad = 'U.P.R.' AND poblacion != 'MALAGA';
UPDATE vehiculos SET unidad = 'BPSC - UPR 1' WHERE unidad = 'U.P.R.-1';
UPDATE vehiculos SET unidad = 'BPSC - UPR 2' WHERE unidad = 'U.P.R.-2';
UPDATE vehiculos SET unidad = 'UDYCO' WHERE unidad = 'udyco - Costa del Sol';
UPDATE vehiculos SET unidad = 'BLSC' WHERE unidad = 'B.L.S.C.';
UPDATE vehiculos SET unidad = 'BLPJ' WHERE unidad = 'B.L.P.J.';
UPDATE vehiculos SET unidad = 'BLI' WHERE unidad = 'B.L.I.';
UPDATE vehiculos SET unidad = 'BLPC' WHERE unidad = 'B.L.P.C.';
UPDATE vehiculos SET unidad = 'BLEF' WHERE unidad = 'B.L.E. y F.';
UPDATE vehiculos SET unidad = 'UCOL' WHERE unidad = 'U.C.O.L.';
UPDATE vehiculos SET unidad = 'JEFATURA COMISARIA' WHERE unidad = 'JEFE COMISARÍA';
UPDATE vehiculos SET unidad = 'UDYCO' WHERE unidad = 'udyco - Costa del Sol' AND poblacion != 'MALAGA';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'BAJA';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'ENAJ. DIRECTA MARZO 2025 BAJA ASIA';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'ENAJ. COCHES';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'DESTRUIDO' OR unidad = 'DESTRUCCION';
UPDATE vehiculos SET unidad = '' WHERE unidad = 'BAJA EN ASIA' OR unidad = 'BAJA TEMPORAL' OR unidad = 'BANCO DE VEHCULOS' OR unidad = 'BAJA DEFINITIVA' OR unidad = 'BAJA ASIA Enaj. MOTOS' OR unidad = 'BAJA ASIA Enaj. COCHES';
UPDATE vehiculos SET unidad = '' WHERE unidad LIKE '%CURSO%';
CREATE TABLE documentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vehiculo_id INTEGER NOT NULL REFERENCES vehiculos(id) ON DELETE CASCADE,
nombre TEXT,
filename TEXT NOT NULL,
fecha DATETIME DEFAULT (datetime('now','localtime'))
);
CREATE TABLE mensajes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texto TEXT NOT NULL,
autor TEXT,
fecha DATETIME DEFAULT (datetime('now','localtime'))
);
ALTER TABLE "Historial" RENAME TO "historial_tmp";
ALTER TABLE "historial_tmp" RENAME TO "historial";
ALTER TABLE historial RENAME COLUMN Matricula TO matricula;
ALTER TABLE historial RENAME COLUMN Unidad TO unidad;
ALTER TABLE historial RENAME COLUMN Cuando TO fecha;
ALTER TABLE historial RENAME COLUMN Observaciones TO observaciones;
ALTER TABLE historial ADD COLUMN id INTEGER;
ALTER TABLE historial ADD COLUMN vehiculo_id INTEGER;
ALTER TABLE historial ADD COLUMN situacion TEXT;
ALTER TABLE historial ADD COLUMN poblacion TEXT;
ALTER TABLE historial ADD COLUMN grupo TEXT;
UPDATE historial SET fecha = substr(fecha, 1, 10);
UPDATE vehiculos
SET
m_oficial = NULLIF(m_oficial, 'none'),
m_reservada = NULLIF(m_reservada, 'none'),
m_propia = NULLIF(m_propia, 'none'),
VIN = NULLIF(VIN, 'none'),
poliza = NULLIF(poliza, 'none'),
viat = NULLIF(viat, 'none'),
legajo = NULLIF(legajo, 'none'),
marca = NULLIF(marca, 'none'),
modelo = NULLIF(modelo, 'none'),
tipo = NULLIF(tipo, 'none'),
color = NULLIF(color, 'none'),
combustible = NULLIF(combustible, 'none'),
plazas = NULLIF(plazas, 'none'),
situacion = NULLIF(situacion, 'none'),
poblacion = NULLIF(poblacion, 'none'),
unidad = NULLIF(unidad, 'none'),
grupo = NULLIF(grupo, 'none'),
int_operacion = NULLIF(int_operacion, 'none'),
int_grupo = NULLIF(int_grupo, 'none'),
int_juzgado = NULLIF(int_juzgado, 'none'),
int_procedimiento = NULLIF(int_procedimiento, 'none'),
f_alta = NULLIF(f_alta, 'none'),
f_baja = NULLIF(f_baja, 'none'),
km = NULLIF(km, 'none'),
km_1 = NULLIF(km_1, 'none'),
itv_ultima = NULLIF(itv_ultima, 'none'),
itv = NULLIF(itv, 'none'),
notas = NULLIF(notas, 'none');
————————————————————————
Después:
1- Poner id autoincremental en tabla vehículos
2. Poner id autoincremental en tabla historial
3- Hacer el update:
UPDATE historial
SET vehiculo_id = (
SELECT id
FROM vehiculos
WHERE vehiculos.m_oficial = historial.matricula
);
4- Ordenar los campos de las tablas, con este orden:
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#currentColor" 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>

After

Width:  |  Height:  |  Size: 1.2 KiB

+55
View File
@@ -0,0 +1,55 @@
id,tipo,denominacion
1,AUTOCAR 21 A 40 PAX,
2,AUTOCAR > 40 PAX,
3,FURGON,
4,"CAMION < 3,5 TM",
5,"CAMION > 3,5 TM",
6,CANIOB DE CABALLOS,
7,CELULAR,
8,CICLOMOTOR,
9,CTRL. MOVIL FRONTERA,
10,EMBARCACION,
11,ESTACIONES ITV,
12,FURGON APOLO,
13,FURGON EDEX,
14,FURGON G.CANINOS,
15,FURGON GOES,
16,FURGON MINI APOLO,
17,FURGON U.P.R.,
18,FURGON UNIPOL F-8,
19,FURGONETA COMBI,
20,FURGONETA DE CARGA,
21,FURGONETA DNI,
22,FURGONETA DNI RURAL,
23,FURGONETA IZ,
24,FURGONETA LABORATORIO DE DOCUMENTOS FALSOS,
25,FURGONETA MIXTA,
26,FURGONETA P.CIENTIFICA,
27,FURGONETA SUBSUELO,
28,GRUA,
29,GRUPO ELECTROGENO,
30,LANZA-AGUA,
31,MAXI-Z,
32,MICROBUS 10 A 20 PAX,
33,MOTOCICLETA TIPO K,
34,MOTOCICLETA TIPO Q,
35,MOTOCICLETA UNIFORMADA,
36,POLICIA TECN.ESPECIALIDAD,
37,PUESTO DE MANDO AVANZADO,
38,REMOLQUE <750 KG,
39,REMOLQUE >750 KG,
40,TANQUETA,
41,TODO CAMINO,
42,TODO TERRENO,
43,TRACTOR TIPO EINSA,
44,TRANSPORTE SANITARIO,
45,TRASLADO EXTRANJEROS,
46,TURISMO K,
47,TURISMO Q,
48,TURISMO Z,
49,TURISMO Z COORDINACION,
50,UNIDAD MOVIL DE DOCUMENTACION (VIDOC),
51,UNIDAD MOVIL FRONTERAS,
52,VARIOS SIN CLASIFICAR,
53,VEHICULO ELECTRICO NO HOMOLOGADO,
54,VEHICULO ELECTRICO UNIF. HOMOLOGADO,
1 id tipo denominacion
2 1 AUTOCAR 21 A 40 PAX
3 2 AUTOCAR > 40 PAX
4 3 FURGON
5 4 CAMION < 3,5 TM
6 5 CAMION > 3,5 TM
7 6 CANIOB DE CABALLOS
8 7 CELULAR
9 8 CICLOMOTOR
10 9 CTRL. MOVIL FRONTERA
11 10 EMBARCACION
12 11 ESTACIONES ITV
13 12 FURGON APOLO
14 13 FURGON EDEX
15 14 FURGON G.CANINOS
16 15 FURGON GOES
17 16 FURGON MINI APOLO
18 17 FURGON U.P.R.
19 18 FURGON UNIPOL F-8
20 19 FURGONETA COMBI
21 20 FURGONETA DE CARGA
22 21 FURGONETA DNI
23 22 FURGONETA DNI RURAL
24 23 FURGONETA IZ
25 24 FURGONETA LABORATORIO DE DOCUMENTOS FALSOS
26 25 FURGONETA MIXTA
27 26 FURGONETA P.CIENTIFICA
28 27 FURGONETA SUBSUELO
29 28 GRUA
30 29 GRUPO ELECTROGENO
31 30 LANZA-AGUA
32 31 MAXI-Z
33 32 MICROBUS 10 A 20 PAX
34 33 MOTOCICLETA TIPO K
35 34 MOTOCICLETA TIPO Q
36 35 MOTOCICLETA UNIFORMADA
37 36 POLICIA TECN.ESPECIALIDAD
38 37 PUESTO DE MANDO AVANZADO
39 38 REMOLQUE <750 KG
40 39 REMOLQUE >750 KG
41 40 TANQUETA
42 41 TODO CAMINO
43 42 TODO TERRENO
44 43 TRACTOR TIPO EINSA
45 44 TRANSPORTE SANITARIO
46 45 TRASLADO EXTRANJEROS
47 46 TURISMO K
48 47 TURISMO Q
49 48 TURISMO Z
50 49 TURISMO Z COORDINACION
51 50 UNIDAD MOVIL DE DOCUMENTACION (VIDOC)
52 51 UNIDAD MOVIL FRONTERAS
53 52 VARIOS SIN CLASIFICAR
54 53 VEHICULO ELECTRICO NO HOMOLOGADO
55 54 VEHICULO ELECTRICO UNIF. HOMOLOGADO
@@ -1,55 +0,0 @@
tipo,denominacion
AUTOCAR 21 A 40 PAX,
AUTOCAR > 40 PAX,
FURGON,
"CAMION < 3,5 TM",
"CAMION > 3,5 TM",
CANIOB DE CABALLOS,
CELULAR,
CICLOMOTOR,
CTRL. MOVIL FRONTERA,
EMBARCACION,
ESTACIONES ITV,
FURGON APOLO,
FURGON EDEX,
FURGON G.CANINOS,
FURGON GOES,
FURGON MINI APOLO,
FURGON U.P.R.,
FURGON UNIPOL F-8,
FURGONETA COMBI,
FURGONETA DE CARGA,
FURGONETA DNI,
FURGONETA DNI RURAL,
FURGONETA IZ,
FURGONETA LABORATORIO DE DOCUMENTOS FALSOS,
FURGONETA MIXTA,
FURGONETA P.CIENTIFICA,
FURGONETA SUBSUELO,
GRUA,
GRUPO ELECTROGENO,
LANZA-AGUA,
MAXI-Z,
MICROBUS 10 A 20 PAX,
MOTOCICLETA TIPO K,
MOTOCICLETA TIPO Q,
MOTOCICLETA UNIFORMADA,
POLICIA TECN.ESPECIALIDAD,
PUESTO DE MANDO AVANZADO,
REMOLQUE <750 KG,
REMOLQUE >750 KG,
TANQUETA,
TODO CAMINO,
TODO TERRENO,
TRACTOR TIPO EINSA,
TRANSPORTE SANITARIO,
TRASLADO EXTRANJEROS,
TURISMO K,
TURISMO Q,
TURISMO Z,
TURISMO Z COORDINACION,
UNIDAD MOVIL DE DOCUMENTACION (VIDOC),
UNIDAD MOVIL FRONTERAS,
VARIOS SIN CLASIFICAR ,
VEHICULO ELECTRICO NO HOMOLOGADO ,
VEHICULO ELECTRICO UNIF. HOMOLOGADO
1 tipo denominacion
2 AUTOCAR 21 A 40 PAX
3 AUTOCAR > 40 PAX
4 FURGON
5 CAMION < 3,5 TM
6 CAMION > 3,5 TM
7 CANIOB DE CABALLOS
8 CELULAR
9 CICLOMOTOR
10 CTRL. MOVIL FRONTERA
11 EMBARCACION
12 ESTACIONES ITV
13 FURGON APOLO
14 FURGON EDEX
15 FURGON G.CANINOS
16 FURGON GOES
17 FURGON MINI APOLO
18 FURGON U.P.R.
19 FURGON UNIPOL F-8
20 FURGONETA COMBI
21 FURGONETA DE CARGA
22 FURGONETA DNI
23 FURGONETA DNI RURAL
24 FURGONETA IZ
25 FURGONETA LABORATORIO DE DOCUMENTOS FALSOS
26 FURGONETA MIXTA
27 FURGONETA P.CIENTIFICA
28 FURGONETA SUBSUELO
29 GRUA
30 GRUPO ELECTROGENO
31 LANZA-AGUA
32 MAXI-Z
33 MICROBUS 10 A 20 PAX
34 MOTOCICLETA TIPO K
35 MOTOCICLETA TIPO Q
36 MOTOCICLETA UNIFORMADA
37 POLICIA TECN.ESPECIALIDAD
38 PUESTO DE MANDO AVANZADO
39 REMOLQUE <750 KG
40 REMOLQUE >750 KG
41 TANQUETA
42 TODO CAMINO
43 TODO TERRENO
44 TRACTOR TIPO EINSA
45 TRANSPORTE SANITARIO
46 TRASLADO EXTRANJEROS
47 TURISMO K
48 TURISMO Q
49 TURISMO Z
50 TURISMO Z COORDINACION
51 UNIDAD MOVIL DE DOCUMENTACION (VIDOC)
52 UNIDAD MOVIL FRONTERAS
53 VARIOS SIN CLASIFICAR
54 VEHICULO ELECTRICO NO HOMOLOGADO
55 VEHICULO ELECTRICO UNIF. HOMOLOGADO
+5 -11
View File
@@ -1,4 +1,4 @@
id,unidad,poblacion_id
id,unidad,poblacion_id
1,AEROPUERTO,1
2,AUTOMOCION,1
32,BPEF,1
@@ -19,14 +19,15 @@ id,unidad,poblacion_id
47,BPI - TEDAX NRBQ,1
48,BPPC,1
49,BPPJ,1
313,BPPJ - UDEV,1
50,BPPJ - UDEV ATRACOS,1
51,BPPJ - UDEV AUDIENCIA PROVINCIAL,1
52,BPPJ - UDEV CIBERDELINCUENCIA 1,1
53,BPPJ - UDEV CIBERDELINCUENCIA 2,1
54,BPPJ - UDEV HOMICIDIOS,1
55,BPPJ - UDEV HURTOS,1
56,BPPJ - UDEV JEFE,1
57,BPPJ - UDEV JEFE SECCION,1
56,BPPJ - UDEV JEFATURA,1
57,BPPJ - UDEV JEFATURA SECCION,1
58,BPPJ - UDEV ROBOS 1,1
59,BPPJ - UDEV ROBOS 2,1
60,BPPJ - UDEV UFAM GRUME,1
@@ -61,7 +62,7 @@ id,unidad,poblacion_id
89,SECRETARIA GENERAL - INSTALACIONES,1
90,SECRETARIA GENERAL - MATERIAL,1
91,SECRETARIA GENERAL - TELECOMUNICACIONES,1
92,SECRETARIA GENERAL- NUCLEO RESERVA,1
92,SECRETARIA GENERAL - NUCLEO RESERVA,1
96,UCOP,1
97,UCOP - GABINETE TECNICO,1
98,UCOP - GOES,1
@@ -103,7 +104,6 @@ id,unidad,poblacion_id
157,BLPJ - HURTOS,2
158,BLPJ - INFORMES,2
159,BLPJ - JEFATURA,2
160,BLPJ - PATRIMONIO,2
161,BLPJ - PEQUEÑO TRAFICO,2
162,BLPJ - PERSONAS,2
163,BLPJ - ROBOS,2
@@ -118,7 +118,6 @@ id,unidad,poblacion_id
176,BLI,3
177,BLPC,3
178,BLPJ,3
179,BLPJ - PATRIMONIO,3
180,BLPJ - DELINCUENCIA ECONOMICA,3
181,BLPJ - DELINCUENCIA URBANA,3
183,BLPJ - FRAUDES,3
@@ -140,7 +139,6 @@ id,unidad,poblacion_id
199,BLI,4
200,BLPC,4
201,BLPJ,4
202,BLPJ - PATRIMONIO,4
203,BLPJ - DELINCUENCIA ECONOMICA,4
205,BLPJ - DELINCUENCIA URBANA,4
206,BLPJ - FRAUDES,4
@@ -162,7 +160,6 @@ id,unidad,poblacion_id
222,BLI,5
223,BLPC,5
224,BLPJ,5
225,BLPJ - PATRIMONIO,5
226,BLPJ - DELINCUENCIA ECONOMICA,5
228,BLPJ - DELINCUENCIA URBANA,5
229,BLPJ - FRAUDES,5
@@ -184,7 +181,6 @@ id,unidad,poblacion_id
245,BLI,6
246,BLPC,6
247,BLPJ,6
248,BLPJ - PATRIMONIO,6
249,BLPJ - DELINCUENCIA ECONOMICA,6
251,BLPJ - DELINCUENCIA URBANA,6
252,BLPJ - FRAUDES,6
@@ -206,7 +202,6 @@ id,unidad,poblacion_id
268,BLI,7
269,BLPC,7
270,BLPJ,7
271,BLPJ - PATRIMONIO,7
272,BLPJ - DELINCUENCIA ECONOMICA,7
274,BLPJ - DELINCUENCIA URBANA,7
275,BLPJ - FRAUDES,7
@@ -228,7 +223,6 @@ id,unidad,poblacion_id
291,BLI,8
292,BLPC,8
293,BLPJ,8
294,BLPJ - PATRIMONIO,8
295,BLPJ - DELINCUENCIA ECONOMICA,8
297,BLPJ - DELINCUENCIA URBANA,8
298,BLPJ - FRAUDES,8
1 id unidad poblacion_id
2 1 AEROPUERTO 1
3 2 AUTOMOCION 1
4 32 BPEF 1
19 47 BPI - TEDAX NRBQ 1
20 48 BPPC 1
21 49 BPPJ 1
22 313 BPPJ - UDEV 1
23 50 BPPJ - UDEV ATRACOS 1
24 51 BPPJ - UDEV AUDIENCIA PROVINCIAL 1
25 52 BPPJ - UDEV CIBERDELINCUENCIA 1 1
26 53 BPPJ - UDEV CIBERDELINCUENCIA 2 1
27 54 BPPJ - UDEV HOMICIDIOS 1
28 55 BPPJ - UDEV HURTOS 1
29 56 BPPJ - UDEV JEFE BPPJ - UDEV JEFATURA 1
30 57 BPPJ - UDEV JEFE SECCION BPPJ - UDEV JEFATURA SECCION 1
31 58 BPPJ - UDEV ROBOS 1 1
32 59 BPPJ - UDEV ROBOS 2 1
33 60 BPPJ - UDEV UFAM GRUME 1
62 89 SECRETARIA GENERAL - INSTALACIONES 1
63 90 SECRETARIA GENERAL - MATERIAL 1
64 91 SECRETARIA GENERAL - TELECOMUNICACIONES 1
65 92 SECRETARIA GENERAL- NUCLEO RESERVA SECRETARIA GENERAL - NUCLEO RESERVA 1
66 96 UCOP 1
67 97 UCOP - GABINETE TECNICO 1
68 98 UCOP - GOES 1
104 157 BLPJ - HURTOS 2
105 158 BLPJ - INFORMES 2
106 159 BLPJ - JEFATURA 2
160 BLPJ - PATRIMONIO 2
107 161 BLPJ - PEQUEÑO TRAFICO 2
108 162 BLPJ - PERSONAS 2
109 163 BLPJ - ROBOS 2
118 176 BLI 3
119 177 BLPC 3
120 178 BLPJ 3
179 BLPJ - PATRIMONIO 3
121 180 BLPJ - DELINCUENCIA ECONOMICA 3
122 181 BLPJ - DELINCUENCIA URBANA 3
123 183 BLPJ - FRAUDES 3
139 199 BLI 4
140 200 BLPC 4
141 201 BLPJ 4
202 BLPJ - PATRIMONIO 4
142 203 BLPJ - DELINCUENCIA ECONOMICA 4
143 205 BLPJ - DELINCUENCIA URBANA 4
144 206 BLPJ - FRAUDES 4
160 222 BLI 5
161 223 BLPC 5
162 224 BLPJ 5
225 BLPJ - PATRIMONIO 5
163 226 BLPJ - DELINCUENCIA ECONOMICA 5
164 228 BLPJ - DELINCUENCIA URBANA 5
165 229 BLPJ - FRAUDES 5
181 245 BLI 6
182 246 BLPC 6
183 247 BLPJ 6
248 BLPJ - PATRIMONIO 6
184 249 BLPJ - DELINCUENCIA ECONOMICA 6
185 251 BLPJ - DELINCUENCIA URBANA 6
186 252 BLPJ - FRAUDES 6
202 268 BLI 7
203 269 BLPC 7
204 270 BLPJ 7
271 BLPJ - PATRIMONIO 7
205 272 BLPJ - DELINCUENCIA ECONOMICA 7
206 274 BLPJ - DELINCUENCIA URBANA 7
207 275 BLPJ - FRAUDES 7
223 291 BLI 8
224 292 BLPC 8
225 293 BLPJ 8
294 BLPJ - PATRIMONIO 8
226 295 BLPJ - DELINCUENCIA ECONOMICA 8
227 297 BLPJ - DELINCUENCIA URBANA 8
228 298 BLPJ - FRAUDES 8
+81 -16
View File
@@ -10,6 +10,7 @@
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='car-front.svg') }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/animate.min.css') }}">
@@ -18,17 +19,19 @@
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* original */
:root {
--bg: #0d0f14;
--surface: #13161e;
--nav-bg: #0a0c10;
--border: #1e2230;
--accent: #5c6ef8;
--accent2: #8b5cf6;
--text: #e2e6f0;
--muted: #5a607a;
--pill-bg: #1a1f2e;
--success: #34d399;
--bg: #0a111f;
--surface: #101a2e;
--nav-bg: #080e19;
--border: #1d2b45;
--accent: #3b82f6;
--accent2: #06b6d4;
--text: #e8f0ff;
--muted: #71819c;
--pill-bg: #17243a;
--success: #22c7e8;
--nav-w: 220px;
--radius: 12px;
}
@@ -38,14 +41,73 @@
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #e3e6ef;
--accent: #5c6ef8;
--accent2: #8b5cf6;
--accent: #2563eb;
--accent2: #4f8df7;
--text: #1a1d29;
--muted: #8a8fa3;
--pill-bg: #eef0f8;
--success: #10b981;
--success: #159dcc;
}
/* azul profundo + azul eléctrico + cyan */
/* :root {
--bg: #0d0f14;
--surface: #13161e;
--nav-bg: #0a0c10;
--border: #1e2230;
--accent: #3b82f6;
--accent2: #06b6d4;
--text: #e2e6f0;
--muted: #5a607a;
--pill-bg: #1a1f2e;
--success: #34b9e8;
--nav-w: 220px;
--radius: 12px;
}
[data-theme="light"] {
--bg: #f4f8fd;
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #dce6f2;
--accent: #2563eb;
--accent2: #0891b2;
--text: #172033;
--muted: #718096;
--pill-bg: #eaf2fc;
--success: #0891b2;
} */
/* Ambar + coral */
/* :root {
--bg: #12110f;
--surface: #1b1916;
--nav-bg: #0e0d0b;
--border: #302b25;
--accent: #f59e0b;
--accent2: #f97316;
--text: #f3eee6;
--muted: #8e877d;
--pill-bg: #28231d;
--success: #34d399;
--nav-w: 220px;
--radius: 12px;
}
[data-theme="light"] {
--bg: #faf8f4;
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #e8e1d7;
--accent: #d97706;
--accent2: #ea580c;
--text: #29251f;
--muted: #8a8278;
--pill-bg: #f6eee1;
--success: #059669;
} */
body {
background: var(--bg);
color: var(--text);
@@ -132,7 +194,10 @@
}
.nav-links a:hover { background: var(--border); color: var(--text); }
.nav-links a.active {
background: linear-gradient(135deg, rgba(92,110,248,.18), rgba(139,92,246,.1));
background: linear-gradient(135deg, rgba(30, 144, 255, 0.08), rgba(0, 51, 204, 0.25));
/* background: linear-gradient(135deg, rgba(92,110,248,.18), rgba(139,92,246,.1)); */
/* background: linear-gradient(135deg, rgba(30, 144, 255, 0.25), rgba(0, 51, 204, 0.75)); */
color: var(--text);
border: 1px solid rgba(92,110,248,.25);
}
@@ -645,7 +710,7 @@
</div>
<div class="nav-logo">
<span></span><h3>PyFleet</h3>
<span></span>PyFleet
</div>
<div class="theme-switch-row">
@@ -683,7 +748,7 @@
{% if session.get('mensaje') and page == 'contacto' %}<span class="msg-badge">!</span>{% endif %}
</a>
<div class="nav-submenu" style="margin-top: 100px;">
<div class="nav-submenu">
<hr style="opacity: 0.5;margin-bottom: 5px;">
<button class="nav-submenu-toggle {% if page in ['poblaciones','unidades','tipo'] %}active{% endif %}"
onclick="toggleSubmenu(this)">
+1 -1
View File
@@ -19,7 +19,7 @@
--bg: #f5f6fa;
--surface: #ffffff;
--border: #e3e6ef;
--accent: #5c6ef8;
--accent: #2563eb;
--text: #1a1d29;
--muted: #8a8fa3;
--error-bg: rgba(248,113,113,.1);
+9 -5
View File
@@ -333,17 +333,17 @@
<div class="form-actions" style="margin-top:28px">
<button type="submit" class="btn btn-primary"> <i class="bi bi-floppy" style="font-size: 1rem;"></i>
{{ 'GUARDAR CAMBIOS' if vehiculo else '✚ Registrar vehículo' }}
{{ '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>
<a href="/viat?q={{ q_viat }}" class="btn btn-ghost" style="background-color: gray; color: white;"><i class="bi bi-x-lg"></i>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>
<a href="/itv?desde={{ desde }}&hasta={{ hasta }}&q_itv={{ q_itv }}" class="btn btn-ghost" style="background-color: gray; color: white;"><i class="bi bi-x-lg"></i>CANCELAR</a>
{% else %}
<a href="{{ url_for('vehiculos_list', q=q) if q else url_for('vehiculos_list') }}" class="btn btn-ghost">Cancelar</a>
<a href="{{ url_for('vehiculos_list', q=q) if q else url_for('vehiculos_list') }}" class="btn btn-ghost" style="background-color: gray; color: white;"><i class="bi bi-x-lg"></i>CANCELAR</a>
{% endif %}
<!-- <div style="display:flex;justify-content:space-between;align-items:center;margin-top:12px;"> -->
@@ -412,7 +412,9 @@
<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('') }}">
<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('') }}">
<button type="button" class="btn btn-danger btn-sm"
onclick="confirmarEliminar(this)">Eliminar</button>
</form>
@@ -438,6 +440,8 @@
<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('') }}">
<input type="hidden" name="_origen" value="{{ origen|default('') }}">
<input type="hidden" name="_q_viat" value="{{ q_viat|default('') }}">
<div class="form-grid" style="margin-bottom:12px;">
<div class="form-group" style="margin-bottom:0">
+1 -1
View File
@@ -7,7 +7,7 @@
<div class="page-header">
<div class="page-eyebrow">Base de datos</div>
<div class="page-title"><i class="bi bi-taxi-front"></i> Vehículos</div>
<div class="page-subtitle">Gestión del parque móvil. Búsqueda por m.oficial, m.reservada, VIN, marca, modelo, poblacion, unidad, denominación, tipo, operacion, situacion, lanzadestellos.</div>
<div class="page-subtitle">Gestión del parque móvil. Búsqueda por m.oficial, m.reservada, VIN, marca, modelo, poblacion, unidad, tipo, situacion, lanzadestellos.</div>
</div>
<div class="crud-toolbar">
+880
View File
@@ -0,0 +1,880 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{% block title %}App{% endblock %} · PyFleet</title>
<script>
(function() {
var theme = localStorage.getItem('nexus-theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='car-front.svg') }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/animate.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap-icons-1.13.1/bootstrap-icons.css') }}">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* original */
:root {
--bg: #0a111f;
--surface: #101a2e;
--nav-bg: #080e19;
--border: #1d2b45;
--accent: #3b82f6;
--accent2: #06b6d4;
--text: #e8f0ff;
--muted: #71819c;
--pill-bg: #17243a;
--success: #22c7e8;
--nav-w: 220px;
--radius: 12px;
}
[data-theme="light"] {
--bg: #f5f6fa;
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #e3e6ef;
--accent: #2563eb;
--accent2: #4f8df7;
--text: #1a1d29;
--muted: #8a8fa3;
--pill-bg: #eef0f8;
--success: #159dcc;
}
/* azul profundo + azul eléctrico + cyan */
/* :root {
--bg: #0d0f14;
--surface: #13161e;
--nav-bg: #0a0c10;
--border: #1e2230;
--accent: #3b82f6;
--accent2: #06b6d4;
--text: #e2e6f0;
--muted: #5a607a;
--pill-bg: #1a1f2e;
--success: #34b9e8;
--nav-w: 220px;
--radius: 12px;
}
[data-theme="light"] {
--bg: #f4f8fd;
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #dce6f2;
--accent: #2563eb;
--accent2: #0891b2;
--text: #172033;
--muted: #718096;
--pill-bg: #eaf2fc;
--success: #0891b2;
} */
/* Ambar + coral */
/* :root {
--bg: #12110f;
--surface: #1b1916;
--nav-bg: #0e0d0b;
--border: #302b25;
--accent: #f59e0b;
--accent2: #f97316;
--text: #f3eee6;
--muted: #8e877d;
--pill-bg: #28231d;
--success: #34d399;
--nav-w: 220px;
--radius: 12px;
}
[data-theme="light"] {
--bg: #faf8f4;
--surface: #ffffff;
--nav-bg: #ffffff;
--border: #e8e1d7;
--accent: #d97706;
--accent2: #ea580c;
--text: #29251f;
--muted: #8a8278;
--pill-bg: #f6eee1;
--success: #059669;
} */
body {
background: var(--bg);
color: var(--text);
font-family: 'Inter', sans-serif;
font-size: 15px;
display: flex;
min-height: 100vh;
transition: background .2s ease, color .2s ease;
}
.input-disabled-look {
background-color: #f5f5f5;
color: #6c757d;
border: 1px solid #ced4da;
cursor: not-allowed;
opacity: 0.7;
pointer-events: none;
}
nav, .card, .msg-banner, .msg-form input, .nav-links a {
transition: background .2s ease, border-color .2s ease, color .2s ease;
}
/* ── NAVBAR ── */
nav {
width: var(--nav-w);
background: var(--nav-bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 28px 16px 24px;
position: fixed;
top: 0; left: 0; bottom: 0;
z-index: 10;
}
.nav-logo {
font-family: 'Space Grotesk', sans-serif;
font-size: 20px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.5px;
padding: 0 8px 28px;
display: flex;
align-items: center;
gap: 40px;
}
.nav-logo span.dot {
width: 8px; height: 8px;
background: var(--accent);
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 8px var(--accent);
}
.nav-section-label {
font-size: 10px;
font-weight: 600;
letter-spacing: 1.2px;
text-transform: uppercase;
color: var(--muted);
padding: 0 8px 10px;
}
.nav-links {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.nav-links a {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
text-decoration: none;
color: var(--muted);
font-size: 14px;
font-weight: 500;
transition: background 0.15s, color 0.15s;
}
.nav-links a:hover { background: var(--border); color: var(--text); }
.nav-links a.active {
background: linear-gradient(135deg, rgba(30, 144, 255, 0.08), rgba(0, 51, 204, 0.25));
/* background: linear-gradient(135deg, rgba(92,110,248,.18), rgba(139,92,246,.1)); */
/* background: linear-gradient(135deg, rgba(30, 144, 255, 0.25), rgba(0, 51, 204, 0.75)); */
color: var(--text);
border: 1px solid rgba(92,110,248,.25);
}
.nav-links a .icon {
width: 18px; height: 18px;
opacity: .6;
flex-shrink: 0;
}
.nav-links a.active .icon { opacity: 1; }
/* indicator bar */
.nav-links a.active::before {
content: '';
position: absolute;
left: 16px;
width: 3px; height: 18px;
background: var(--accent);
border-radius: 2px;
margin-left: -12px;
}
.nav-links a { position: relative; }
/* message badge in nav */
.msg-badge {
margin-left: auto;
background: var(--accent);
color: #fff;
font-size: 10px;
font-weight: 600;
padding: 2px 7px;
border-radius: 20px;
}
.nav-footer {
padding-top: 16px;
border-top: 1px solid var(--border);
font-size: 12px;
color: var(--muted);
padding-left: 8px;
}
/* ── THEME SWITCH ── */
.theme-switch-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 8px 16px;
margin-bottom: 4px;
border-bottom: 1px solid var(--border);
}
.theme-switch-label {
font-size: 12px;
font-weight: 500;
color: var(--muted);
display: flex;
align-items: center;
gap: 6px;
}
.theme-toggle {
position: relative;
width: 42px;
height: 23px;
background: var(--pill-bg);
border: 1px solid var(--border);
border-radius: 20px;
cursor: pointer;
flex-shrink: 0;
padding: 0;
}
.theme-toggle::after {
content: '☾';
position: absolute;
top: 1px; left: 1px;
width: 17px; height: 17px;
background: var(--accent);
border-radius: 50%;
transition: transform .25s cubic-bezier(.4,0,.2,1), background .2s;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
line-height: 1;
color: #fff;
}
[data-theme="light"] .theme-toggle::after {
content: '☀';
transform: translateX(19px);
background: #f59e0b;
}
/* ── MAIN CONTENT ── */
main {
margin-left: var(--nav-w);
flex: 1;
padding: 40px 48px;
min-height: 100vh;
}
.page-header {
margin-bottom: 36px;
}
.page-eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 1.4px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 8px;
}
.page-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 28px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.5px;
}
.page-subtitle {
margin-top: 6px;
color: var(--muted);
font-size: 14px;
}
/* ── MENSAJES MÚLTIPLES ── */
.msg-banner-wrap {
background: linear-gradient(135deg, rgba(92,110,248,.1), rgba(139,92,246,.06));
border: 1px solid rgba(92,110,248,.25);
border-radius: var(--radius);
margin-bottom: 24px;
overflow: hidden;
animation: slideIn .25s ease;
}
.msg-banner-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid rgba(92,110,248,.15);
font-size: 11px;
font-weight: 600;
letter-spacing: .7px;
text-transform: uppercase;
color: var(--accent);
}
.msg-clear-all {
background: none;
border: 1px solid rgba(92,110,248,.3);
border-radius: 6px;
color: var(--muted);
font-size: 11px;
padding: 3px 10px;
cursor: pointer;
transition: color .15s, border-color .15s;
}
.msg-clear-all:hover { color: var(--text); border-color: var(--muted); }
.msg-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid rgba(92,110,248,.08);
gap: 12px;
}
.msg-item:last-child { border-bottom: none; }
.msg-fecha {
font-size: 10px;
color: var(--muted);
margin-bottom: 2px;
}
.msg-texto { font-size: 13px; color: var(--text); }
.msg-del-btn {
background: none;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 13px;
padding: 4px 8px;
border-radius: 6px;
flex-shrink: 0;
transition: color .15s, background .15s;
}
.msg-del-btn:hover { color: #f87171; background: rgba(248,113,113,.1); }
/* ── MESSAGE BANNER ── */
.msg-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
background: linear-gradient(135deg, rgba(92,110,248,.12), rgba(139,92,246,.08));
border: 1px solid rgba(92,110,248,.3);
border-radius: var(--radius);
padding: 14px 18px;
margin-bottom: 28px;
animation: slideIn .25s ease;
}
@keyframes slideIn {
from { opacity:0; transform: translateY(-6px); }
to { opacity:1; transform: translateY(0); }
}
.msg-banner .label {
font-size: 11px;
font-weight: 600;
letter-spacing: .8px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 3px;
}
.msg-banner .text { color: var(--text); font-size: 14px; }
.msg-banner a.clear-btn {
color: var(--muted);
font-size: 12px;
text-decoration: none;
white-space: nowrap;
padding: 5px 10px;
border: 1px solid var(--border);
border-radius: 6px;
transition: color .15s, border-color .15s;
flex-shrink: 0;
}
.msg-banner a.clear-btn:hover { color: var(--text); border-color: var(--muted); }
/* ── MESSAGE FORM ── */
.msg-form {
display: flex;
gap: 10px;
margin-top: 28px;
max-width: 560px;
}
.msg-form input {
flex: 1;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: 'Inter', sans-serif;
font-size: 14px;
padding: 10px 14px;
outline: none;
transition: border-color .15s;
}
.msg-form input::placeholder { color: var(--muted); }
.msg-form input:focus { border-color: var(--accent); }
.msg-form button {
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
font-family: 'Inter', sans-serif;
font-size: 13px;
font-weight: 600;
padding: 10px 20px;
cursor: pointer;
transition: opacity .15s;
white-space: nowrap;
}
.msg-form button:hover { opacity: .85; }
/* ── CARDS ── */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
margin-top: 28px;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
transition: border-color .2s;
}
.card:hover { border-color: rgba(92,110,248,.35); }
.card-icon {
font-size: 22px;
margin-bottom: 12px;
}
.card-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 14px;
font-weight: 600;
margin-bottom: 6px;
}
.card-desc { font-size: 13px; color: var(--muted); line-height: 1.5; }
/* ── SELECT / DROPDOWN ── */
.select-wrap {
position: relative;
}
.form-select {
appearance: none;
-webkit-appearance: none;
cursor: pointer;
padding-right: 38px;
}
.form-select option {
background: var(--surface);
color: var(--text);
}
.select-arrow {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
color: var(--muted);
pointer-events: none;
transition: transform .2s;
}
.select-wrap:focus-within .select-arrow {
transform: translateY(-50%) rotate(180deg);
color: var(--accent);
}
.form-select:focus {
border-color: var(--accent);
}
{% block extra_css %}{% endblock %}
thead th {
position: relative;
transition: background .15s, color .15s;
}
thead th:hover {
background: var(--border);
color: var(--text);
}
thead th:hover .sort-icon {
opacity: .7 !important;
}
/* ── CRUD TABLE ── */
.crud-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-wrap {
position: relative;
flex: 1;
max-width: 360px;
}
.search-wrap > svg:first-child {
/* .search-wrap svg { */
position: absolute;
left: 12px; top: 50%;
transform: translateY(-50%);
width: 16px; height: 16px;
color: var(--muted);
pointer-events: none;
}
.search-wrap input {
width: 100%;
padding: 9px 14px 9px 38px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: 'Inter', sans-serif;
font-size: 13px;
outline: none;
transition: border-color .15s;
}
.search-wrap input::placeholder { color: var(--muted); }
.search-wrap input:focus { border-color: var(--accent); }
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 9px 18px; border-radius: 8px; font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 600; cursor: pointer; border: none; text-decoration: none; transition: opacity .15s, background .15s; }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover { opacity: .85; }
.btn-ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-danger { background: transparent; color: #f87171; border: 1px solid rgba(248,113,113,.35); }
.btn-danger:hover { background: rgba(248,113,113,.08); }
.btn-sm { padding: 5px 12px; font-size: 12px; }
.table-wrap {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; }
thead tr { background: var(--pill-bg); }
th {
padding: 12px 16px;
text-align: left;
font-size: 11px;
font-weight: 600;
letter-spacing: .8px;
text-transform: uppercase;
color: var(--muted);
}
td {
padding: 13px 16px;
font-size: 13px;
border-top: 1px solid var(--border);
color: var(--text);
}
tbody tr { transition: background .12s; padding-bottom: }
tbody tr:hover { background: var(--pill-bg); }
.td-id { font-family: monospace; color: var(--muted); font-size: 12px; }
.td-actions { display: flex; gap: 8px; align-items: center; }
.empty-state {
text-align: center;
padding: 48px 24px;
color: var(--muted);
font-size: 14px;
}
.empty-state svg { width: 40px; height: 40px; margin-bottom: 12px; opacity: .3; }
/* ── FORM ── */
.form-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 32px;
max-width: 1560px; /* aqui se establece el ancho de los formularios */
}
.form-group { margin-bottom: 5px; }
.form-label {
display: block;
font-size: 12px;
font-weight: 600;
letter-spacing: .5px;
color: var(--muted);
margin-bottom: 7px;
text-transform: uppercase;
}
.form-input {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: 'Inter', sans-serif;
font-size: 14px;
padding: 10px 14px;
outline: none;
transition: border-color .15s;
}
.nav-submenu-toggle {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 10px 12px;
border-radius: 8px;
background: none;
border: none;
color: var(--muted);
font-family: 'Inter', sans-serif;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background .15s, color .15s;
}
.nav-submenu-toggle:hover,
.nav-submenu-toggle.active { background: var(--border); color: var(--text); }
.nav-submenu-items {
display: none;
flex-direction: column;
gap: 2px;
padding-left: 16px;
margin-top: 2px;
}
.nav-submenu-items.open { display: flex; }
.nav-submenu-items a {
font-size: 13px;
padding: 8px 12px;
}
.form-input::placeholder { color: var(--muted); }
.form-input:focus { border-color: var(--accent); }
.form-input:disabled { opacity: .5; cursor: not-allowed; }
.form-actions { display: flex; gap: 10px; margin-top: 28px; }
/* ── FLASH ── */
.flash { padding: 11px 16px; border-radius: 8px; font-size: 13px; margin-bottom: 20px; animation: slideIn .2s ease; }
.flash.ok { background: rgba(52,211,153,.12); border: 1px solid rgba(52,211,153,.3); color: #34d399; }
.flash.error { background: rgba(248,113,113,.1); border: 1px solid rgba(248,113,113,.3); color: #f87171; }
</style>
</head>
<body>
<!-- class="animate__animated animate__backInLeft" -->
<nav id="navbar"
{% if session.pop("animate_navbar", None) %}
class="animate__animated animate__backInUp"
{% endif %}>
<div style="padding: 0 8px 20px; text-align:center;">
<img src="{{ url_for('static', filename='img/logo.png') }}"
alt="Logo"
style="max-width:160px; width:100%; height:auto;">
</div>
<div class="nav-logo">
<span></span><h3>PyFleet</h3>
</div>
<div class="theme-switch-row">
<span class="theme-switch-label">Tema</span>
<button class="theme-toggle" id="themeToggle" aria-label="Cambiar tema claro/oscuro" type="button"></button>
</div>
<div class="nav-section-label">Navegación</div>
<div class="nav-links">
<a href="{{ url_for('index') }}" class="{% if page == 'inicio' %}active{% endif %}">
<i class="bi bi-house"style="font-size: 1rem;"></i>
Inicio
<!-- {% if session.get('mensaje') and page == 'inicio' %}<span class="msg-badge">!</span>{% endif %} -->
</a>
<a href="{{ url_for('vehiculos_list') }}" class="{% if page == 'vehiculos' %}active{% endif %}">
<i class="bi bi-car-front" style="font-size: 1rem;"></i>
Vehículos
</a>
<a href="{{ url_for('itv_list') }}" class="{% if page == 'itv' %}active{% endif %}">
<i class="bi bi-calendar-check" style="font-size: 1rem;"></i>
ITV
</a>
<a href="{{ url_for('viat_list') }}" class="{% if page == 'viat' %}active{% endif %}">
<i class="bi bi-broadcast" style="font-size: 1rem;"></i>
Via-T
</a>
<a href="{{ url_for('contacto') }}" class="{% if page == 'contacto' %}active{% endif %}">
<i class="bi bi-person-vcard" style="font-size: 1rem;"></i>
Contacto
{% if session.get('mensaje') and page == 'contacto' %}<span class="msg-badge">!</span>{% endif %}
</a>
<div class="nav-submenu" style="margin-top: 100px;">
<hr style="opacity: 0.5;margin-bottom: 5px;">
<button class="nav-submenu-toggle {% if page in ['poblaciones','unidades','tipo'] %}active{% endif %}"
onclick="toggleSubmenu(this)">
<i class="bi bi-database-check" style="font-size: 1rem;"></i>
Configuración
<svg class="chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-top:10px;margin-left:auto;transition:transform .2s">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<div class="nav-submenu-items {% if page in ['poblaciones','unidades','tipo'] %}open{% endif %}">
<a href="{{ url_for('unidades_list') }}" class="{% if page == 'unidades' %}active{% endif %}">
<i class="bi bi-building"></i>
Unidades
</a>
<a href="{{ url_for('tipo_list') }}" class="{% if page == 'tipo' %}active{% endif %}">
<i class="bi bi-bus-front"></i>
Tipo vehiculo
</a>
</div>
</div>
</div>
<div class="nav-footer">
{% if mensajes %}
<a href="{{ url_for('index') }}" style="
display:flex;align-items:center;gap:6px;
color:var(--accent);text-decoration:none;font-size:12px;
padding:6px 8px;border-radius:7px;transition:background .15s;
margin-bottom:4px;"
onmouseover="this.style.background='var(--border)'"
onmouseout="this.style.background=''">
💬 {{ mensajes|length }} mensaje{{ 's' if mensajes|length != 1 }} activo{{ 's' if mensajes|length != 1 }}
</a>
{% else %}
<span style="font-size:12px;padding:6px 8px;display:block;">Sin mensajes</span>
{% endif %}
<a href="{{ url_for('logout') }}" style="
display:flex;align-items:center;gap:8px;margin-top:12px;
color:var(--muted);font-size:13px;text-decoration:none;
padding:7px 8px;border-radius:7px;transition:background .15s,color .15s;
" onmouseover="this.style.background='var(--border)';this.style.color='var(--text)'"
onmouseout="this.style.background='';this.style.color='var(--muted)'">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
Cerrar sesión
</a>
</div>
</nav>
<main>
{# Mensajes solo en página de inicio #}
{% if page == 'inicio' %}
{% if mensajes %}
<div class="msg-banner-wrap">
<div class="msg-banner-header">
<div style="display:flex;align-items:center;gap:8px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
</svg>
<span>{{ mensajes|length }} mensaje{{ 's' if mensajes|length != 1 }}</span>
</div>
<form method="POST" action="{{ url_for('mensajes_eliminar_todos') }}"
onsubmit="return confirm('¿Eliminar todos los mensajes?')">
<button type="submit" class="msg-clear-all">✕ Eliminar todos</button>
</form>
</div>
{% for m in mensajes %}
<div class="msg-item">
<div>
<div class="msg-fecha">{{ m['fecha'] | fecha_es_hora }}</div>
<div class="msg-texto">{{ m['texto'] }}</div>
</div>
<form method="POST" action="{{ url_for('mensaje_eliminar', id=m['id']) }}">
<button type="submit" class="msg-del-btn" title="Eliminar este mensaje">✕</button>
</form>
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<script>
var toggle = document.getElementById('themeToggle');
var html = document.documentElement;
function applyTheme(theme) {
html.setAttribute('data-theme', theme);
localStorage.setItem('nexus-theme', theme);
}
// Sync button state on load
applyTheme(localStorage.getItem('nexus-theme') || 'light');
toggle.addEventListener('click', function() {
var current = html.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
var next = current === 'light' ? 'dark' : 'light';
applyTheme(next);
});
function toggleSubmenu(btn) {
var items = btn.nextElementSibling;
items.classList.toggle('open');
btn.querySelector('.chevron').style.transform =
items.classList.contains('open') ? 'rotate(180deg)' : '';
}
</script>
</body>
</html>
Binary file not shown.
Binary file not shown.