ESEN
Foto de Daniel Martín Hurtado

¡Hola! Soy Daniel Martín Hurtado

#OpenToWork

Estudiante de Ingeniería de Telecomunicaciones con un gran interés en el ámbito tecnológico. Apasionado por la Programación, especialmente por Python, y por la Arquitectura de Internet

Proyectos

Portfolio Creator

Tu información profesional suele estar dispersa: código en GitHub, proyectos en el GitLab de tu universidad o empresa, artículos en bases de datos científicas. Portfolio Creator la junta en un solo sitio, te deja elegir qué mostrar y genera tu CV listo para descargar en PDF o HTML, con resúmenes redactados por IA.

  • HTML
  • CSS
  • Python
  • Django
  • Docker
  • AWS
# ─── CV Profesional Unificado ──────────────────────────────
def _get_cv_data(user):
    """
    Recupera el estado de la 'cesta' del CV.
    Guarda los datos serializados como texto JSON en la tabla ContenidoData,
    usando un recurso especial con nombre 'cv_profesional_username'.
    """
    recurso_name = f"cv_profesional_{user.username}"
    # Busca el registro o lo crea vacío si no existe
    contenido_obj, created = ContenidoData.objects.get_or_create(
        recurso=recurso_name,
        defaults={'usuario': user.username, 'contenido': '{}'}
    )
    try:
        # Deserializar JSON (texto a diccionario Python)
        data = json.loads(contenido_obj.contenido)
    except json.JSONDecodeError:
        data = {}
        
    if not isinstance(data, dict):
        data = {}
    
    # Estructura inicial obligatoria
    if 'personal_info' not in data:
        data['personal_info'] = {'photo': '', 'name': '', 'linkedin': '', 'phone': '', 'email': '', 'about': ''}
    if 'items' not in data:
        data['items'] = []
        
    return contenido_obj, data

@login_required
def cv_builder(request):
    """Renderiza el panel de control del CV unificado (la vista de la 'cesta')"""
    contenido_obj, data = _get_cv_data(request.user)  # Lee la base de datos
    
    if request.method == 'POST':
        # El usuario ha enviado el formulario de "Información Personal"
        import base64
        if 'photo' in request.FILES:  # Si ha subido una foto
            try:
                photo_file = request.FILES['photo']
                # Convierte la foto a Base64: un string gigante que la incrusta directamente en el HTML
                photo_b64 = base64.b64encode(photo_file.read()).decode('utf-8')
                data['personal_info']['photo'] = f"data:{photo_file.content_type};base64,{photo_b64}"
            except Exception:
                pass
        
        # Guarda los textos
        data['personal_info']['name'] = request.POST.get('name', '').strip()
        data['personal_info']['linkedin'] = request.POST.get('linkedin', '').strip()
        data['personal_info']['phone'] = request.POST.get('phone', '').strip()
        data['personal_info']['email'] = request.POST.get('email', '').strip()
        data['personal_info']['about'] = request.POST.get('about', '').strip()
        
        # Vuelve a convertir el diccionario a texto JSON y lo guarda en la BD
        contenido_obj.contenido = json.dumps(data)
        contenido_obj.save()
        messages.success(request, 'Información del CV guardada correctamente.')
        return redirect('cv_builder')

    return render(request, 'portfolioCV/cv_builder.html', {
        'cv_data': data
    })

@login_required
def agregar_al_cv(request):
    """Agrega un repositorio u obra a la cesta del CV (se llama desde un formulario POST)"""
    if request.method == 'POST':
        # Se reciben los datos básicos del repositorio para añadirlos a la 'cesta'
        platform = request.POST.get('platform')
        item_id = request.POST.get('id')
        name = request.POST.get('name')
        
        if platform and item_id and name:
            contenido_obj, data = _get_cv_data(request.user)
            # Evitar duplicados (no añadir la misma repo dos veces)
            if not any(str(item['id']) == str(item_id) and item['platform'] == platform for item in data['items']):
                data['items'].append({  # Añadir al array de JSON
                    'platform': platform,
                    'id': item_id,
                    'name': name
                })
                contenido_obj.contenido = json.dumps(data)  # Guardar
                contenido_obj.save()
                messages.success(request, f'"{name}" añadido a tu CV Profesional.')
            else:
                messages.info(request, f'"{name}" ya estaba en tu CV Profesional.')
                
        # Vuelve a la página anterior (HTTP Referer) o al CV Builder si no hay
        return redirect(request.META.get('HTTP_REFERER', 'cv_builder'))
    return redirect('index')
# ─── CV Profesional Unificado ──────────────────────────────
def _get_cv_data(user):
    """
    Recupera el estado de la 'cesta' del CV.
    Guarda los datos serializados como texto JSON en la tabla ContenidoData,
    usando un recurso especial con nombre 'cv_profesional_username'.
    """
    recurso_name = f"cv_profesional_{user.username}"
    # Busca el registro o lo crea vacío si no existe
    contenido_obj, created = ContenidoData.objects.get_or_create(
        recurso=recurso_name,
        defaults={'usuario': user.username, 'contenido': '{}'}
    )
    try:
        # Deserializar JSON (texto a diccionario Python)
        data = json.loads(contenido_obj.contenido)
    except json.JSONDecodeError:
        data = {}
        
    if not isinstance(data, dict):
        data = {}
    
    # Estructura inicial obligatoria
    if 'personal_info' not in data:
        data['personal_info'] = {'photo': '', 'name': '', 'linkedin': '', 'phone': '', 'email': '', 'about': ''}
    if 'items' not in data:
        data['items'] = []
        
    return contenido_obj, data

@login_required
def cv_builder(request):
    """Renderiza el panel de control del CV unificado (la vista de la 'cesta')"""
    contenido_obj, data = _get_cv_data(request.user)  # Lee la base de datos
    
    if request.method == 'POST':
        # El usuario ha enviado el formulario de "Información Personal"
        import base64
        if 'photo' in request.FILES:  # Si ha subido una foto
            try:
                photo_file = request.FILES['photo']
                # Convierte la foto a Base64: un string gigante que la incrusta directamente en el HTML
                photo_b64 = base64.b64encode(photo_file.read()).decode('utf-8')
                data['personal_info']['photo'] = f"data:{photo_file.content_type};base64,{photo_b64}"
            except Exception:
                pass
        
        # Guarda los textos
        data['personal_info']['name'] = request.POST.get('name', '').strip()
        data['personal_info']['linkedin'] = request.POST.get('linkedin', '').strip()
        data['personal_info']['phone'] = request.POST.get('phone', '').strip()
        data['personal_info']['email'] = request.POST.get('email', '').strip()
        data['personal_info']['about'] = request.POST.get('about', '').strip()
        
        # Vuelve a convertir el diccionario a texto JSON y lo guarda en la BD
        contenido_obj.contenido = json.dumps(data)
        contenido_obj.save()
        messages.success(request, 'Información del CV guardada correctamente.')
        return redirect('cv_builder')

    return render(request, 'portfolioCV/cv_builder.html', {
        'cv_data': data
    })

@login_required
def agregar_al_cv(request):
    """Agrega un repositorio u obra a la cesta del CV (se llama desde un formulario POST)"""
    if request.method == 'POST':
        # Se reciben los datos básicos del repositorio para añadirlos a la 'cesta'
        platform = request.POST.get('platform')
        item_id = request.POST.get('id')
        name = request.POST.get('name')
        
        if platform and item_id and name:
            contenido_obj, data = _get_cv_data(request.user)
            # Evitar duplicados (no añadir la misma repo dos veces)
            if not any(str(item['id']) == str(item_id) and item['platform'] == platform for item in data['items']):
                data['items'].append({  # Añadir al array de JSON
                    'platform': platform,
                    'id': item_id,
                    'name': name
                })
                contenido_obj.contenido = json.dumps(data)  # Guardar
                contenido_obj.save()
                messages.success(request, f'"{name}" añadido a tu CV Profesional.')
            else:
                messages.info(request, f'"{name}" ya estaba en tu CV Profesional.')
                
        # Vuelve a la página anterior (HTTP Referer) o al CV Builder si no hay
        return redirect(request.META.get('HTTP_REFERER', 'cv_builder'))
    return redirect('index')

Experiencia Laboral

  1. Actual

    Restaurante Chincha Internacional

    • Diseño y puesta en marcha de la web con sistema de reservas online
    • Posicionamiento local en Google Maps y Apple Maps
    • Gestión de redes sociales y campañas (Meta Ads, YouTube Ads)
    Visitar web
  2. Auditorio Salem Madrid

    • Realizador de emisiones en directo con NewTek Tricaster
    • Gestión de cámaras manuales y PTZ
  3. Restaurante Kechua

    • Redes sociales y publicidad digital
    • Diseño de cartas y menús para el restaurante

Sobre mí

Hola,

Soy Daniel Martín Hurtado, estudiante del último año del grado de Ingeniería de Telecomunicaciones, con especialidad en Audio y Multimedia.

Gran apasionado del sector de las redes y la telemática, me encanta saber por qué funcionan las cosas y qué hay detrás de ellas. He trabajado con programas como NetGUI, aprendiendo a gestionar redes desde el terminal. Mi segunda gran pasión es Python, me encanta ese amor y odio de programar hasta encontrar una solución. Me encantaría combinar ambas pasiones, sería increíble ver todo lo que puede salir de ahí.

Deseo aprender mucho más y dar lo mejor de mí para aprender.

Atentamente,

Daniel Martín Hurtado