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
48 changes: 48 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Pages deploy (prod & preview)

on:
push:
branches:
- master
workflow_dispatch: {}

concurrency:
group: pages-deploy
cancel-in-progress: true

env:
NEXT_TELEMETRY_DISABLED: 1

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies (include dev)
run: npm install --include=dev

- name: Build static export
run: |
unset NEXT_BASE_PATH
unset NEXT_ASSET_PREFIX
NODE_ENV=production npm run export

- name: Deploy to gh-pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: ./out
keep_files: false
destination_dir: .
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: "deploy: ${{ github.ref_name }}"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.next/
out/
43 changes: 43 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
### ROL

Actúa como un **Senior Frontend Architect y UI/UX Strategist** especializado en modernización de sistemas legacy. Eres experto en el ecosistema React (Next.js/Vite), Tailwind CSS y Shadcn UI, así como en patrones de migración desde Angular.

### CONTEXTO

Actualmente tengo un portafolio estático construido en **Angular + Bootstrap** alojado en GitHub Pages.
**Mi Objetivo:** Migrar completamente este proyecto a un stack moderno usando **React + Tailwind CSS + Shadcn UI**.
**Mi Perfil:** Soy desarrollador Full Stack (NodeJS, Express/NestJS, PostgreSQL), pero quiero que este portafolio siga siendo una solución estática/ligera (sin backend dedicado para el sitio en sí) para mantener la eficiencia y bajo costo.

### TAREA

Analiza el código Angular proporcionado y guía el proceso de refactorización y rediseño en 3 fases:

#### FASE 1: Estrategia de Migración (Angular -> React)

Analiza la estructura de mis componentes y servicios en Angular y propón su equivalencia en React:

1. **Mapeo de Componentes:** Identifica qué componentes de Angular pueden fusionarse o dividirse al pasar a React (Functional Components + Hooks).
2. **Routing:** Cómo traducir mi configuración de rutas actual a `react-router` o al App Router de Next.js (si recomiendas Next.js para SSG).
3. **Gestión de Estado:** Si ves complejidad innecesaria en Angular, sugiere cómo simplificarla con React Context o Zustand, o simplemente props.

#### FASE 2: Modernización de UI (Bootstrap -> Tailwind/Shadcn)

No quiero una traducción literal del diseño. Propón un "Look & Feel" moderno:

1. **Reemplazo de Bootstrap:** Identifica los elementos de Bootstrap (Grid, Modals, Navbars) y dime qué componentes exactos de **Shadcn UI** debo usar para reemplazarlos.
2. **Estilizado:** Sugiere una paleta de colores y tipografía que combine profesionalismo con modernidad, aprovechando las utilidades de Tailwind.
3. **Showcase de Backend:** Dado que soy experto en Backend (Node/Postgres), sugiere cómo puedo representar visualmente estas habilidades en el frontend (ej. ¿debería incluir diagramas de arquitectura de mis proyectos en lugar de solo capturas de pantalla?).

#### FASE 3: Validación de Contenido y Datos

ANTES de generar código final, realiza una entrevista de validación. Hazme preguntas para actualizar la data:

- Pregunta sobre proyectos recientes en Node/NestJS que no estén en el portafolio antiguo.
- Pregunta qué secciones del portafolio actual ya no me representan y deberíamos eliminar.
- Pregunta sobre mis preferencias de estructura de carpetas para el nuevo proyecto React (ej. Feature-based vs Type-based).

### FORMATO DE SALIDA

- Usa **Markdown** con jerarquía clara.
- En las sugerencias de código, usa comentarios para explicar _por qué_ este enfoque de React es mejor que el original de Angular.
- Piensa paso a paso: Primero analiza la arquitectura, luego el diseño visual, y finalmente el contenido.
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# [jedabero.github.io](http://jedabero.github.io)

This is my github presentation page.

#TODO:
- Add a list of the projects I'm working on which code is hosted on github.
- About me
- Random stuff
25 changes: 25 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
color-scheme: dark;
--page-bg: radial-gradient(circle at 20% 20%, rgba(95, 227, 192, 0.08), transparent 35%),
radial-gradient(circle at 80% 20%, rgba(122, 162, 255, 0.08), transparent 35%),
radial-gradient(circle at 50% 80%, rgba(95, 227, 192, 0.06), transparent 35%);
}

body {
min-height: 100vh;
background-color: #0b1021;
background-image: var(--page-bg, none);
color: #e4e7f5;
}

.bg-grid {
background-image:
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
background-size: 48px 48px, 48px 48px;
background-position: center;
}
33 changes: 33 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Metadata } from 'next';
import { Space_Grotesk, Manrope } from 'next/font/google';
import './globals.css';

const display = Space_Grotesk({
subsets: ['latin'],
variable: '--font-display'
});

const body = Manrope({
subsets: ['latin'],
variable: '--font-body'
});

export const metadata: Metadata = {
title: 'Jedabero | Full Stack Engineer',
description:
'Portafolio enfocado en React, React Native, Node/Nest/Express y PostgreSQL. Casos de estudio privados.'
};

export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="es">
<body className={`${display.variable} ${body.variable} font-body bg-page-gradient bg-base-bg text-base-text`}>
{children}
</body>
</html>
);
}
21 changes: 21 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { About } from '@/components/sections/about';
import { Architecture } from '@/components/sections/architecture';
import { Contact } from '@/components/sections/contact';
import { Hero } from '@/components/sections/hero';
import { Projects } from '@/components/sections/projects';

export default function HomePage() {
return (
<main className="min-h-screen">
<Navbar />
<div className="bg-page-gradient bg-grid">
<Hero />
<About />
<Projects />
<Architecture />
<Contact />
</div>
</main>
);
}
import { Navbar } from '@/components/sections/navbar';
73 changes: 73 additions & 0 deletions app/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { CompanyLogo } from '@/components/company-logo';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs } from '@/components/ui/tabs';
import { caseStudies } from '@/lib/data/case-studies';
import { notFound } from 'next/navigation';

type Params = { slug: string };

export function generateStaticParams() {
return caseStudies.map((item) => ({ slug: item.slug }));
}

export default function CaseStudyPage({ params }: { params: Params }) {
const study = caseStudies.find((item) => item.slug === params.slug);
if (!study) return notFound();

const tabs = [
{ id: 'context', label: 'Contexto', content: study.problem },
{ id: 'solution', label: 'Solución', content: study.solution },
{
id: 'impact',
label: 'Impacto',
content: (
<ul className="list-disc pl-4 space-y-1">
{study.impact.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
)
}
];

return (
<main className="min-h-screen py-16 md:py-20">
<div className="max-w-4xl mx-auto px-4 space-y-8">
<div className="flex items-center gap-3">
<CompanyLogo companyId={study.companyId} size={40} />
<div>
<p className="text-sm text-base-muted uppercase tracking-[0.2em]">{study.company}</p>
<h1 className="text-3xl font-semibold font-display text-base-text">{study.title}</h1>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Badge variant="blue">{study.domain}</Badge>
{study.frontend.map((item) => (
<Badge key={item} variant="mint">
Frontend: {item}
</Badge>
))}
{study.backend.map((item) => (
<Badge key={item} variant="mint">
Backend: {item}
</Badge>
))}
{study.infra.map((item) => (
<Badge key={item} variant="muted">
{item}
</Badge>
))}
</div>
<Card>
<CardHeader>
<CardTitle>Detalles</CardTitle>
</CardHeader>
<CardContent>
<Tabs tabs={tabs} />
</CardContent>
</Card>
</div>
</main>
);
}
40 changes: 40 additions & 0 deletions components/company-logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type CompanyLogoProps = {
companyId: 'fullstack-labs' | 'pluriza' | 'idi';
size?: number;
};

export function CompanyLogo({ companyId, size = 32 }: CompanyLogoProps) {
const common = {
width: size,
height: size,
viewBox: '0 0 64 64',
role: 'img'
};

if (companyId === 'fullstack-labs') {
return (
<svg {...common} aria-label="Fullstack Labs">
<rect x="6" y="6" width="52" height="52" rx="12" fill="none" stroke="#5fe3c0" strokeWidth="4" />
<path d="M16 20h20v8H16zM16 34h28v8H16zM16 48h16v8H16z" fill="#7aa2ff" />
</svg>
);
}

if (companyId === 'pluriza') {
return (
<svg {...common} aria-label="Pluriza">
<circle cx="22" cy="32" r="14" fill="none" stroke="#7aa2ff" strokeWidth="4" />
<circle cx="42" cy="24" r="10" fill="none" stroke="#5fe3c0" strokeWidth="4" />
<circle cx="42" cy="44" r="10" fill="none" stroke="#5fe3c0" strokeWidth="4" />
</svg>
);
}

return (
<svg {...common} aria-label="Fundación IDI">
<rect x="10" y="10" width="44" height="44" rx="8" fill="none" stroke="#7aa2ff" strokeWidth="4" />
<path d="M18 46c4-8 8-12 14-12s10 4 14 12" fill="none" stroke="#5fe3c0" strokeWidth="4" />
<circle cx="32" cy="26" r="6" fill="#5fe3c0" />
</svg>
);
}
33 changes: 33 additions & 0 deletions components/sections/about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Badge } from '@/components/ui/badge';
import { getExperienceYears } from '@/lib/data/experience';

const coreStack = ['React', 'React Native', 'Node.js', 'NestJS', 'Express', 'PostgreSQL', 'GraphQL'];

export function About() {
const years = getExperienceYears();
return (
<section id="about" className="py-16 md:py-20 bg-gradient-to-b from-base-surface/40 to-transparent">
<div className="max-w-5xl mx-auto px-4 grid gap-10 md:grid-cols-[1.2fr_0.8fr] items-center">
<div className="space-y-4">
<p className="text-sm uppercase tracking-[0.2em] text-base-muted">Sobre mí</p>
<h2 className="text-3xl font-semibold text-base-text font-display">Hola, soy Jedabero</h2>
<p className="text-base text-base-muted">
Ingeniero de Sistemas con {years}+ años construyendo productos web y móviles. Me enfoco en arquitecturas
ligeras, DX y performance en React/React Native con backends en Express, Next.js o NestJS sobre PostgreSQL.
</p>
<p className="text-base text-base-muted">
Trabajo con equipos distribuidos para entregar features sostenibles, con medición continua y buenas
prácticas listas para escalar.
</p>
</div>
<div className="flex flex-wrap gap-2">
{coreStack.map((item) => (
<Badge key={item} variant={item === 'PostgreSQL' ? 'blue' : 'mint'}>
{item}
</Badge>
))}
</div>
</div>
</section>
);
}
46 changes: 46 additions & 0 deletions components/sections/architecture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { DiagramArrow, DiagramBlock } from '@/lib/diagrams/blocks';

const diagrams = [
{
title: 'Web + Mobile',
flow: ['Client (React/React Native)', 'BFF (Next.js/NestJS/Express)', 'PostgreSQL', 'Redis/S3'],
accent: ['mint', 'blue', 'mint', 'blue'] as const
},
{
title: 'API GraphQL',
flow: ['Clients', 'Node.js + GraphQL', 'Data Layer', 'Observabilidad'],
accent: ['blue', 'mint', 'blue', 'mint'] as const
}
];

export function Architecture() {
return (
<section id="architecture" className="py-16 md:py-20 bg-base-surface/30">
<div className="max-w-6xl mx-auto px-4 space-y-8">
<div className="space-y-2">
<p className="text-sm uppercase tracking-[0.2em] text-base-muted">Arquitectura</p>
<h2 className="text-3xl font-semibold text-base-text font-display">Diagramas ligeros</h2>
<p className="text-base text-base-muted max-w-3xl">
Referencias rápidas de cómo estructuro productos: BFFs en Node/GraphQL o Next.js, data en Postgres y
caching donde aporta más. Sin assets sensibles; solo bloques conceptuales.
</p>
</div>
<div className="grid gap-6 md:grid-cols-2">
{diagrams.map((item) => (
<div key={item.title} className="rounded-xl border border-white/10 bg-base-bg/60 p-5">
<h3 className="text-xl font-semibold text-base-text font-display mb-4">{item.title}</h3>
<div className="flex items-center justify-center gap-2 flex-wrap">
{item.flow.map((label, idx) => (
<div key={label} className="flex items-center gap-2">
<DiagramBlock label={label} accent={item.accent[idx] === 'blue' ? 'blue' : 'mint'} />
{idx < item.flow.length - 1 && <DiagramArrow from={label} to={item.flow[idx + 1]} />}
</div>
))}
</div>
</div>
))}
</div>
</div>
</section>
);
}
Loading
Loading