IZAN ESPINOSA
> ADMINISTRADOR DE SISTEMAS Y REDES | CCST CYBERSECURITY - CISCO
Administrador de sistemas enfocado en la gestión de infraestructura, seguridad y redes. Cuento con una sólida base de conocimientos técnicos consolidados tanto de forma autónoma como durante el desarrollo de mis prácticas profesionales en el sector tecnológico, asumiendo responsabilidades de soporte crítico, gestión de incidencias y despliegues.
[ CERTIFICACIONES Y CREDENCIALES ]
Cisco Certified Support Technician (CCST) Cybersecurity
Cisco — Credencial Activa
CCNA: Switching, Routing, and Wireless Essentials
Cisco | Enrutamiento, Alta disponibilidad
Linux II: Advanced System Services
Cisco | Gestión de datos, Servicios esenciales
Ethical Hacker
Cisco | Ciberseguridad avanzada
Introduction to Cybersecurity
Cisco | Detección de amenazas
Curso Básico de Ciberseguridad
CCN-CERT Centro Criptológico Nacional
CCNA1: Introduction to Networks
Cisco | Ethernet, IP Connectivity
NDG Linux Essentials
Cisco Partner | Linux Core
IT Essentials
Cisco | Sistemas operativos, Redes
[Networking]
- ■ Enrutamiento
- ■ IP connectivity
- ■ IP Subnetting
- ■ Ethernet
- ■ Protocolos de conmutación
- ■ Alta disponibilidad
- ■ Protocolo de Redundancia de Primer Salto (FHRP)
- ■ IP Services
- ■ Servicios de propiedad intelectual
- ■ Conceptos de redes
[Systems Administration]
- ■ Linux
- ■ Linux Virtual Server
- ■ Shells
- ■ Bash
- ■ Servicios esenciales del sistema
- ■ Gestión de datos
- ■ Proxmox
- ■ Windows
- ■ Windows Server
- ■ Administración Local de Sistemas Windows
- ■ Administración de Dominios Windows
- ■ Powershell
- ■ Sistemas operativos
[Cybersecurity & Auditing]
- ■ Ciberseguridad
- ■ Fundamentos de Seguridad de Red
- ■ Seguridad del sistema
- ■ Vulnerabilidades de la red
- ■ Detección de amenazas
- ■ Privacidad y confidencialidad de datos
- ■ Mejores prácticas cibernéticas
[Enterprise IT Operations]
- ■ Gestión de incidencias
- ■ Soporte técnico
- ■ Resolución de problemas
- ■ Configuración de sistemas
- ■ Plataformado de equipos
- ■ JIRA
- ■ Gestión documental
- ■ SQL
- ■ XML
- ■ JSON
- ■ HTML5
- ■ Hojas de estilos en cascada (CSS)
Solutel Prácticas
Desarrollo de competencias técnicas avanzadas en redes y soporte de infraestructura empresarial, consolidando entornos con Proxmox y arquitecturas distribuidas.
S2 Grupo Técnico de Microinformática (FCT)
Gestión integral de incidencias de empleados, optimización de flujos técnicos y planificación y asignación estratégica de equipamiento microinformático a sedes.
IES Font de Sant Lluís | Finalización: Junio 2027
IES Font de Sant Lluís | 2023 - 2025
[ PROYECTO DESTACADO: PETCARE360 ]
Diseño, despliegue e integración total de la infraestructura IT y seguridad perimetral para una clínica y tienda de animales orientada a producción. Unifica virtualización en Proxmox VE, dominio con Active Directory, automatización de sistemas y auditoría de redes.
import mysql.connector
from mysql.connector import Error
import tkinter as tk
from tkinter import messagebox, ttk
# Diccionario para guardar el mapa de los productos { "Nombre del Producto": id_producto }
diccionario_productos = {}
# Carga los productos de la base de datos en el desplegable al iniciar
def cargar_productos_desplegable():
global diccionario_productos
try:
conexion = mysql.connector.connect(
host="10.0.0.200",
user="root",
password="1dosTres#",
database="tiendamascotas"
)
cursor = conexion.cursor()
# Agarro el ID y el Nombre para mapearlos
cursor.execute("SELECT id_producto, nombre FROM PRODUCTO ORDER BY nombre;")
nombres_productos = []
diccionario_productos.clear()
for id_prod, nombre in cursor.fetchall():
nombres_productos.append(nombre)
diccionario_productos[nombre] = id_prod # Guardamos la relación
# Insertamos los nombres en el Combobox de la interfaz
combo_producto['values'] = nombres_productos
if nombres_productos:
combo_producto.current(0) # Selecciona el primero por defecto
cursor.close()
conexion.close()
except mysql.connector.Error as err:
messagebox.showerror("Error de Inicio", f"No se ha podido cargar los productos en el menú:\n{err}")
# Funcion para ejecutar las consultas
def ejecutar_consulta(tipo_consulta):
global tabla
try:
# Conexión al Windows Server
conexion = mysql.connector.connect(
host="10.0.0.200",
user="root",
password="1dosTres#",
database="tiendamascotas"
)
cursor = conexion.cursor()
# Captura los datos de las casillas de texto en tiempo real
dni_input = entry_dni.get().strip()
# Obtenemos el id desde el despegable usando el diccionario anteriormente creado
producto_seleccionado = combo_producto.get()
prod_input = diccionario_productos.get(producto_seleccionado)
# Vaciamos y reseteamos por completo la tabla para cambiar las columnas sin errores
tabla.destroy()
tabla = ttk.Treeview(frame_tabla, show="headings")
tabla.pack(side="left", fill="both", expand=True)
scrollbar.config(command=tabla.yview)
tabla.configure(yscrollcommand=scrollbar.set)
# Mapeo de las Consultas
if tipo_consulta == 1:
if not dni_input: return messagebox.showwarning("Falta Dato", "Introduce un DNI en la casilla")
columnas = ("Tipo de Producto", "Cantidad Total Comprada")
query = """SELECT PRODUCTO.tipo_producto, SUM(CONTIENE.cantidad)
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
JOIN PEDIDO ON CONTIENE.id_pedido = PEDIDO.id_pedido
WHERE PEDIDO.dni_cliente = %s
GROUP BY PRODUCTO.tipo_producto
ORDER BY SUM(CONTIENE.cantidad) DESC
LIMIT 1;"""
cursor.execute(query, (dni_input,))
elif tipo_consulta == 2:
if not dni_input: return messagebox.showwarning("Falta Dato", "Introduce un DNI en la casilla")
columnas = ("ID Pedido", "Fecha Pedido", "Nombre", "Cantidad", "Precio Venta", "Total Factura")
query = """SELECT PEDIDO.id_pedido, PEDIDO.fecha_pedido,
PRODUCTO.nombre, CONTIENE.cantidad,
PRODUCTO.precio_venta, (CONTIENE.cantidad * PRODUCTO.precio_venta)
FROM PEDIDO
JOIN CONTIENE ON PEDIDO.id_pedido = CONTIENE.id_pedido
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PEDIDO.dni_cliente = %s
ORDER BY PEDIDO.id_pedido;"""
cursor.execute(query, (dni_input,))
elif tipo_consulta == 3:
if not dni_input: return messagebox.showwarning("Falta Dato", "Introduce un DNI en la casilla")
columnas = ("Total Gastado (€)",)
query = """SELECT SUM(CONTIENE.cantidad * PRODUCTO.precio_venta)
FROM PEDIDO
JOIN CONTIENE ON PEDIDO.id_pedido = CONTIENE.id_pedido
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PEDIDO.dni_cliente = %s;"""
cursor.execute(query, (dni_input,))
elif tipo_consulta == 4:
if not dni_input: return messagebox.showwarning("Falta Dato", "Introduce un DNI en la casilla")
columnas = ("Nombre", "Tipo Animal", "Fecha Nacimiento")
query = """SELECT nombre, tipo_animal, fecha_nacimiento
FROM MASCOTA WHERE dni_cliente = %s;"""
cursor.execute(query, (dni_input,))
elif tipo_consulta == 5:
if not prod_input: return messagebox.showwarning("Falta Dato", "Selecciona un producto válido")
columnas = ("Nombre Proveedor", "Teléfono")
query = """SELECT PROVEEDOR.nombre, PROVEEDOR.telefono
FROM PROVEEDOR
JOIN SUMINISTRA ON PROVEEDOR.id_proveedor = SUMINISTRA.id_proveedor
WHERE SUMINISTRA.id_producto = %s;"""
cursor.execute(query, (prod_input,))
elif tipo_consulta == 6:
if not prod_input: return messagebox.showwarning("Falta Dato", "Selecciona un producto válido")
columnas = ("Nombre Producto", "Ganancia Total")
query = """SELECT PRODUCTO.nombre,
SUM(CONTIENE.cantidad * (PRODUCTO.precio_venta - PRODUCTO.precio_coste))
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PRODUCTO.id_producto = %s
GROUP BY PRODUCTO.nombre;"""
cursor.execute(query, (prod_input,))
elif tipo_consulta == 7:
columnas = ("nombre", "SUM(CONTIENE.cantidad)")
query = """SELECT PRODUCTO.nombre, SUM(CONTIENE.cantidad)
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
GROUP BY PRODUCTO.id_producto, PRODUCTO.nombre
ORDER BY SUM(CONTIENE.cantidad) DESC
LIMIT 5;"""
cursor.execute(query)
# Configuración del tamaño y alineación de las columnas
tabla["columns"] = columnas
for col in columnas:
tabla.heading(col, text=col)
if col in ["nombre", "Nombre Producto", "Producto", "Nombre Proveedor"]:
tabla.column(col, anchor="w", width=380)
else:
tabla.column(col, anchor="center", width=160)
# Pintamos los números en Tkinter
for fila in cursor.fetchall():
valores_limpios = []
for celda in fila:
if celda is None:
valores_limpios.append("0")
elif isinstance(celda, (bytes, bytearray)):
valores_limpios.append(celda.decode('utf-8'))
else:
valores_limpios.append(str(celda))
tabla.insert("", tk.END, values=valores_limpios)
cursor.close()
conexion.close()
except mysql.connector.Error as err:
messagebox.showerror("Error MySQL", f"Hubo un problema con la base de datos:\n{err}")
# Diseño de la interfaz
root = tk.Tk()
root.title("PetCare IT - Cuadro de Mandos de Ventas")
root.geometry("850x600")
root.configure(bg="#f4f6f9")
lbl_titulo = tk.Label(root, text="🐾 PetCare360 🐾", font=("Arial", 16, "bold"), bg="#f4f6f9", fg="#2c3e50")
lbl_titulo.pack(pady=10)
# Cuadro superior de entradas de texto / filtros
frame_inputs = tk.LabelFrame(root, text=" Filtros de Búsqueda ", font=("Arial", 10, "bold"), bg="#f4f6f9", fg="#34495e", padx=10, pady=10)
frame_inputs.pack(fill="x", padx=20, pady=5)
tk.Label(frame_inputs, text="DNI Cliente:", font=("Arial", 10), bg="#f4f6f9").grid(row=0, column=0, padx=5)
entry_dni = tk.Entry(frame_inputs, font=("Arial", 10), width=15)
entry_dni.grid(row=0, column=1, padx=15)
entry_dni.insert(0, "11111111A")
# Despegable
tk.Label(frame_inputs, text="Seleccionar Producto:", font=("Arial", 10), bg="#f4f6f9").grid(row=0, column=2, padx=5)
combo_producto = ttk.Combobox(frame_inputs, font=("Arial", 10), width=35, state="readonly")
combo_producto.grid(row=0, column=3, padx=15)
# Cuadro del menú con los botones
frame_botones = tk.LabelFrame(root, text=" Consultas Disponibles ", font=("Arial", 10, "bold"), bg="#f4f6f9", fg="#34495e", padx=10, pady=10)
frame_botones.pack(fill="x", padx=20, pady=10)
tk.Button(frame_botones, text="1. Tipo Prod. Más Comprado", bg="#3498db", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(1)).grid(row=0, column=0, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="2. Ver Facturas Completas", bg="#3498db", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(2)).grid(row=0, column=1, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="3. Total Gastado Cliente", bg="#3498db", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(3)).grid(row=0, column=2, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="4. Mascotas de Cliente", bg="#3498db", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(4)).grid(row=0, column=3, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="5. Teléfonos Proveedores", bg="#2ecc71", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(5)).grid(row=1, column=0, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="6. Ganancias de Producto", bg="#2ecc71", fg="white", font=("Arial", 9), command=lambda: ejecutar_consulta(6)).grid(row=1, column=1, padx=5, pady=5, sticky="we")
tk.Button(frame_botones, text="🔥 7. TOP 5 Más Vendidos", bg="#e67e22", fg="white", font=("Arial", 9, "bold"), command=lambda: ejecutar_consulta(7)).grid(row=1, column=2, columnspan=2, padx=5, pady=5, sticky="we")
for i in range(4):
frame_botones.grid_columnconfigure(i, weight=1)
# El contenedor inferior de la tabla
frame_tabla = tk.Frame(root, bg="#f4f6f9")
frame_tabla.pack(fill="both", expand=True, padx=20, pady=10)
tabla = ttk.Treeview(frame_tabla, show="headings")
tabla.pack(side="left", fill="both", expand=True)
scrollbar = ttk.Scrollbar(frame_tabla, orient="vertical", command=tabla.yview)
tabla.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
# Hacemos una llamada al despegable nada mas arrancamos el programa
cargar_productos_desplegable()
root.mainloop()
1. Tipo de producto más comprado por un cliente en concreto
SELECT PRODUCTO.tipo_producto, SUM(CONTIENE.cantidad)
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
JOIN PEDIDO ON CONTIENE.id_pedido = PEDIDO.id_pedido
WHERE PEDIDO.dni_cliente = '11111111A'
GROUP BY PRODUCTO.tipo_producto
ORDER BY SUM(CONTIENE.cantidad) DESC
LIMIT 1;
2. Las facturas completas de un cliente en concreto
SELECT PEDIDO.id_pedido, PEDIDO.fecha_pedido, PRODUCTO.nombre, CONTIENE.cantidad,
PRODUCTO.precio_venta, (CONTIENE.cantidad * PRODUCTO.precio_venta)
FROM PEDIDO
JOIN CONTIENE ON PEDIDO.id_pedido = CONTIENE.id_pedido
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PEDIDO.dni_cliente = '11111111A'
ORDER BY PEDIDO.id_pedido;
3. Cuanto dinero se ha gastado un cliente en concreto
SELECT SUM(CONTIENE.cantidad * PRODUCTO.precio_venta)
FROM PEDIDO
JOIN CONTIENE ON PEDIDO.id_pedido = CONTIENE.id_pedido
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PEDIDO.dni_cliente = '11111111A';
4. Las mascotas de un cliente
SELECT nombre, tipo_animal, fecha_nacimiento
FROM MASCOTA
WHERE dni_cliente = '11111111A';
5. Los teléfonos de los proveedores de un producto en concreto
SELECT PROVEEDOR.nombre, PROVEEDOR.telefono
FROM PROVEEDOR
JOIN SUMINISTRA ON PROVEEDOR.id_proveedor = SUMINISTRA.id_proveedor
WHERE SUMINISTRA.id_producto = 1;
6. Ganancias generadas por un producto en concreto
SELECT PRODUCTO.nombre,
SUM(CONTIENE.cantidad * (PRODUCTO.precio_venta - PRODUCTO.precio_coste))
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
WHERE PRODUCTO.id_producto = 1
GROUP BY PRODUCTO.nombre;
7. Top 5 de los productos más vendidos
SELECT PRODUCTO.nombre, SUM(CONTIENE.cantidad)
FROM CONTIENE
JOIN PRODUCTO ON CONTIENE.id_producto = PRODUCTO.id_producto
GROUP BY PRODUCTO.id_producto, PRODUCTO.nombre
ORDER BY SUM(CONTIENE.cantidad) DESC
LIMIT 5;
;
#!/bin/bash
# Configurmos las rutas
ORIGEN="/mnt/respaldo_windows/"
DESTINO="/home/administrador/backups_datos/"
LOG="/var/log/backup_rsync.log"
# Crea la carpeta de destino si no existe
mkdir -p $DESTINO
echo "--- Inicio de Backup: $(date) ---" | tee a $LOG
#rsync:
-a (archivo: mantiene permisos y fechas)
-v (detallado: escribe lo que hace en el log)
-z (comprime durante la transferencia)
--delete (borra en destino lo que no está en origen)
rsync -avz --delete $ORIGEN $DESTINO | tee -a $LOG
echo "Fin de Backup: $(date)" | tee -a $LOG
¿Buscas un perfil técnico, proactivo y respaldado por certificaciones oficiales Cisco? Conecta conmigo de inmediato: