Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
run: npm run build:version
- name: Build Project
run: npm run build
env:
VITE_GA4_ID: ${{ secrets.VITE_GA4_ID }}
- name: Upload artifact to enable deployment
uses: actions/upload-artifact@v4
with:
Expand Down
61 changes: 61 additions & 0 deletions docs/analytics-custom-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# [Analytics] Rastrear eventos personalizados en GA4

## Contexto

El tracking base de Google Analytics (GA4) ya está configurado en `index.html`
con el Measurement ID `G-QPYBS8SFGX` (inyectado vía `VITE_GA4_ID`). Este issue
cubre el rastreo de interacciones específicas del portfolio para entender cómo
los usuarios usan el contenido.

## Eventos a implementar

### 1. Cambio de idioma
- **Evento**: `language_changed`
- **Lugar**: `src/components/features/popupLanguageSelector.jsx`
- **Parámetros**: `{ language: 'en' | 'es' }`
- **Cuándo**: al seleccionar un idioma en el popup Language

### 2. Apertura de detalle de proyecto
- **Evento**: `project_open`
- **Lugar**: `src/App.jsx` (función `toggle`)
- **Parámetros**: `{ company, project }`
- **Cuándo**: al hacer clic en un proyecto de Work Experience

### 3. Click en redes/contacto
- **Evento**: `contact_click`
- **Lugar**: `src/components/features/contact.jsx`
- **Parámetros**: `{ network: 'linkedin' | 'github' | 'twitter' | 'email' }`
- **Cuándo**: al hacer clic en cada enlace del panel Contact
- **Nota**: si **Enhanced Measurement** está habilitado en el Data Stream, los clics
salientes (LinkedIn/GitHub/X) ya se rastrean automáticamente como *outbound
clicks*. Este evento solo aportaría el parámetro `network` y el caso del email
(mailto). Evaluar si vale la pena duplicar el tracking.

### 4. Descarga de CV (PDF)
- **Evento**: `download_cv`
- **Lugar**: `src/constants/popup.js` (acción File > Print)
- **Parámetros**: `{ language: 'en' | 'es' }`
- **Cuándo**: al descargar el PDF del perfil

### 5. Apertura de About
- **Evento**: `about_open`
- **Lugar**: `src/App.jsx` (submenú About)
- **Parámetros**: ninguno
- **Cuándo**: al abrir Help > About

## Implementación

- Crear utilidad `src/services/analytics.js` con helper `trackEvent(name, params)`
que llame a `window.gtag`.
- Integrar la llamada en cada punto indicado.
- Verificar en GA4 > Realtime que los eventos aparezcan.

## Criterios de aceptación

- [ ] Helper `trackEvent` en `src/services/analytics.js`
- [ ] `language_changed` con parámetro `language`
- [ ] `project_open` con `company` y `project`
- [ ] `contact_click` con `network`
- [ ] `download_cv` con `language`
- [ ] `about_open`
- [ ] Eventos visibles en GA4 Realtime tras deploy
90 changes: 90 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,99 @@
<html lang="en">
<head>
<meta charset="UTF-8" />

<!-- Google tag (gtag.js) -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=%VITE_GA4_ID%"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "%VITE_GA4_ID%");
</script>

<link rel="icon" type="image/png" href="/rj.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="index, follow" />
<meta name="author" content="Raul Jonatan Tola" />
<meta
name="description"
content="Raul Jonatan - Full Stack Engineer with 7+ years of experience building web platforms across fintech, ERP, government and corporate projects. React, Angular, Spring, Node.js, PostgreSQL and more."
/>
<meta name="theme-color" content="#2563eb" />
<link rel="canonical" href="https://julnarot.github.io/" />
<title>Raul Jonatan | Full stack engineering</title>

<!-- Open Graph -->
<meta property="og:type" content="profile" />
<meta property="og:site_name" content="Raul Jonatan" />
<meta property="og:title" content="Raul Jonatan | Full Stack Engineer" />
<meta
property="og:description"
content="Full Stack Engineer with 7+ years of experience building web platforms across fintech, ERP, government and corporate projects."
/>
<meta property="og:url" content="https://julnarot.github.io/" />
<meta
property="og:image"
content="https://avatars.githubusercontent.com/u/2840577?v=4"
/>
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="es_ES" />

<!-- Twitter -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Raul Jonatan | Full Stack Engineer" />
<meta
name="twitter:description"
content="Full Stack Engineer with 7+ years of experience building web platforms across fintech, ERP, government and corporate projects."
/>
<meta
name="twitter:image"
content="https://avatars.githubusercontent.com/u/2840577?v=4"
/>

<!-- hreflang -->
<link rel="alternate" hreflang="en" href="https://julnarot.github.io/" />
<link rel="alternate" hreflang="es" href="https://julnarot.github.io/" />
<link rel="alternate" hreflang="x-default" href="https://julnarot.github.io/" />

<!-- PWA -->
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/rj.png" />

<!-- JSON-LD: Person -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Raul Jonatan",
"url": "https://julnarot.github.io/",
"jobTitle": "Full Stack Engineer",
"image": "https://avatars.githubusercontent.com/u/2840577?v=4",
"email": "mailto:rauljhonatan.tola@gmail.com",
"sameAs": [
"https://linkedin.com/in/julnarot",
"https://github.com/julnarot",
"https://x.com/julnarot"
],
"knowsAbout": [
"React",
"Angular",
"Spring Framework",
"Node.js",
"Python",
"PostgreSQL",
"Oracle",
"Docker",
"CI/CD",
"Full Stack Development"
]
}
</script>
</head>
<body>
<div id="root"></div>
Expand Down
54 changes: 54 additions & 0 deletions public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>404 - Not Found | Raul Jonatan</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #1e3a8a;
color: #fff;
font-family: monospace;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.window {
border: 4px double #fff;
background: #d1d5db;
color: #111;
padding: 24px;
max-width: 480px;
width: 90%;
box-shadow: 16px 23px 0 -9px #000;
}
.window h1 {
color: #b91c1c;
margin-bottom: 8px;
}
.window p {
margin-bottom: 12px;
}
.window a {
color: #1e3a8a;
font-weight: bold;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="window">
<h1>404: FILE NOT FOUND</h1>
<p>The resource you are looking for does not exist on this profile.</p>
<p>Press [<a href="/">ENTER</a>] to return to the main profile.</p>
<p>&gt; Raul Jonatan | Full Stack Engineer_</p>
</div>
</body>
</html>
17 changes: 17 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Raul Jonatan | Full Stack Engineer",
"short_name": "Raul Jonatan",
"description": "Personal portfolio of Raul Jonatan, Full Stack Engineer.",
"start_url": "/",
"display": "standalone",
"background_color": "#2563eb",
"theme_color": "#2563eb",
"icons": [
{
"src": "/rj.png",
"sizes": "any",
"type": "image/png",
"purpose": "any"
}
]
}
4 changes: 4 additions & 0 deletions public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://julnarot.github.io/sitemap.xml
9 changes: 9 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://julnarot.github.io/</loc>
<lastmod>2026-08-13</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
</urlset>
8 changes: 6 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ function App() {
src="https://avatars.githubusercontent.com/u/2840577?v=4"
alt="@Julnarot"
className="max-h-40 my-2"
loading="lazy"
width="160"
height="160"
/>
{/* <p className="my-4">{t("main.greeting")}</p> */}
<p className="my-4">{t("main.professionalCareer")}</p>
Expand All @@ -97,12 +100,13 @@ function App() {
{item.values.map((exp, iexp) => (
<div key={iexp} className=" text-sm inline">
[
<a
<button
type="button"
className="cursor-pointer font-bold underline decoration-wavy"
onClick={() => toggle(exp)}
>
{exp.proyect.toUpperCase()}
</a>
</button>
]{iexp < item.values.length - 1 ? " " : ""}
</div>
))}{" "}
Expand Down
8 changes: 4 additions & 4 deletions src/components/features/contact.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export default function Contact({ onClose }) {
<div className="absolute -top-3 right-3 bg-cyan-400 dark:bg-purple-900 px-1 text-sm"> [<span className="text-green-400">&#8593;</span>]</div>
<div className="p-2 pt-3 text-blue-600 dark:text-white selection:bg-cyan-600 h-full overflow-auto
[&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-bluen-600 [&::-webkit-scrollbar-thumb]:bg-cyan-500">
<a href='https://linkedin.com/in/julnarot' target='_black' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Linkedin</a>
<a href='https://github.com/julnarot' target='_black' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Github</a>
<a href='https://x.com/julnarot' target='_black' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Twitter</a>
<a href='mailto:rauljhonatan.tola@gmail.com' target='_black' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(@) Write me</a>
<a href='https://linkedin.com/in/julnarot' target='_blank' rel='noopener noreferrer' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Linkedin</a>
<a href='https://github.com/julnarot' target='_blank' rel='noopener noreferrer' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Github</a>
<a href='https://x.com/julnarot' target='_blank' rel='noopener noreferrer' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(🌏︎) Twitter</a>
<a href='mailto:rauljhonatan.tola@gmail.com' rel='noopener noreferrer' className="flex cursor-pointer font-bold no-underline hover:bg-lime-500 hover:text-white">(@) Write me</a>
</div>
</div>
</div>
Expand Down
61 changes: 42 additions & 19 deletions src/constants/profile.es.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ export const WORK_EXPERIENCE_ES = [
name: "Apliqué buenas prácticas de código limpio, asegurando que fuera legible, mantenible, escalable y testeable.",
skills: ["Desarrollo de Software"],
},
{
name: "Configuré entorno local de desarrollo con base de datos local para reemplazar la conexión remota, asegurando el cumplimiento de normativas de privacidad.",
skills: ["Desarrollo de Software", "Integración y Despliegue"],
},
{
name: "Documenté manuales de uso para cada requerimiento entregado.",
skills: ["Gestión de Proyectos y Planificación"],
Expand Down Expand Up @@ -158,6 +154,17 @@ export const WORK_EXPERIENCE_ES = [
],
},
],
achievements: [
{
challenge: "Acceso limitado a la información",
solution:
"Implementé un entorno local de persistencia de datos alternativo fácil de usar e importar información",
benefit:
"Permitió que el equipo de desarrollo continuara trabajando sin interrupciones y cumpliera con sus objetivos de manera efectiva",
type: "Aporte",
primary: true,
},
],
startDate: "2024-10-01T12:00:00.000Z",
endDate: "2025-02-01T12:00:00.000Z",
typeEmployment: "Contrato",
Expand All @@ -176,18 +183,6 @@ export const WORK_EXPERIENCE_ES = [
name: "Apliqué principios de desarrollo de software enfocados en la mantenibilidad y testabilidad del código.",
skills: ["Desarrollo de Software"],
},
{
name: "Analicé y diseñé un módulo de formularios dinámicos para automatizar la creación, visualización y recopilación de información en cada fase del proceso de solicitud.",
skills: ["Análisis y Diseño de Software"],
},
{
name: "Implementé una prueba de concepto para la configuración y visualización dinámica de formularios de solicitud.",
skills: ["Desarrollo de Software"],
},
{
name: "Participé en múltiples procesos de evaluación técnica para Frontend Angular Developer como entrevistador, evaluando la aptitud de los postulantes para el proyecto.",
skills: ["Evaluación Técnica y Entrevistas"],
},
],
tecnologies: [
{
Expand Down Expand Up @@ -234,9 +229,36 @@ export const WORK_EXPERIENCE_ES = [
name: "Sonar Qube",
skills: ["Desarrollo de Software"],
},
{
name: "Microsoft Word | Excel",
skills: ["Evaluación Técnica y Entrevistas"],
],
achievements: [
{
challenge:
"Configurar un módulo para crear formularios dinámicos con entradas configurables",
problem:
"Visualizar el contenido del formulario con entradas de diferentes tipos según la solicitud seleccionada",
solution:
"Analicé, Diseñé e Implementé una prueba de concepto (demo funcional) que simulaba el funcionamiento de visualización de entradas previamente configuradas",
benefit:
"Permitió al equipo funcional evaluar la solución y tener una solición alternativa para validar su implementación con el cliente",
type: "Reto",
primary: true,
},
{
challenge: "Asignar formulario dinámico a un estado de proceso",
solution:
"Presenté un diseño de negocio como propuesta para relacionar formulario dinámico con flujo según estado de solicitud",
benefit: "Aporte coMejoró la integración del módulo con el marco de procesos",
type: "Aporte",
primary: true,
},
{
challenge: "Evaluar candidatos técnicos para un equipo",
solution:
"Evalué a candidatos técnicos y generé informes de cumplimiento con el perfil",
benefit:
"Contribuí a la selección de candidatos técnicos de alta calidad",
type: "Aporte",
primary: true,
},
],
startDate: "2024-03-01T12:00:00.000Z",
Expand Down Expand Up @@ -312,6 +334,7 @@ export const WORK_EXPERIENCE_ES = [
skills: ["Desarrollo de Software", "Integración y Despliegue"],
},
],

startDate: "2022-10-01T12:00:00.000Z",
endDate: "2024-02-01T12:00:00.000Z",
typeEmployment: "Contrato",
Expand Down
2 changes: 1 addition & 1 deletion src/constants/version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = "1.5.1";
export const VERSION = "1.5.2";
Loading