ESEN
Photo of Daniel Martín Hurtado

Hi! I'm Daniel Martín Hurtado

#OpenToWork

Student of Telecommunications Engineering with a strong interest in technology. Passionate about programming, especially Python, and Internet Architecture

Projects

Portfolio Creator

Your professional information is usually scattered: code on GitHub, projects on your university's or company's GitLab, papers in scientific databases. Portfolio Creator brings it all together in one place, lets you choose what to show, and generates a CV ready to download as PDF or HTML, with AI-written summaries.

  • 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')

Work Experience

  1. Current

    Chincha Internacional Restaurant

    • Designed and launched the website with an online booking system
    • Local SEO on Google Maps and Apple Maps
    • Social media management and ad campaigns (Meta Ads, YouTube Ads)
    Visit website
  2. Salem Madrid Auditorium

    • Live broadcast director using NewTek TriCaster
    • Operation of manual and PTZ cameras
  3. Kechua Restaurant

    • Social media and digital advertising
    • Designed the restaurant's menus

About me

Hi,

I'm Daniel Martín Hurtado, a final-year student of Telecommunications Engineering, specialising in Audio and Multimedia.

I'm passionate about networking and telematics; I love understanding why things work and what lies behind them. I've worked with tools like NetGUI, learning to manage networks from the terminal. My second great passion is Python: I love that love-hate feeling of coding until I find a solution. I'd love to combine both passions; it would be amazing to see what could come out of it.

I want to keep learning and give my best every day.

Kind regards,

Daniel Martín Hurtado