<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Henry Bernardo Cardenas</title>
    <description>The latest articles on DEV Community by David Henry Bernardo Cardenas (@davidbernardo).</description>
    <link>https://dev.to/davidbernardo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4126877%2Fcdca9f2a-1640-4a31-8be8-8b5afc3d210f.jpg</url>
      <title>DEV Community: David Henry Bernardo Cardenas</title>
      <link>https://dev.to/davidbernardo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidbernardo"/>
    <language>en</language>
    <item>
      <title>Cómo construí mi portafolio en 4 frameworks (React 19, Vue 3.5, Angular 19 y Compose HTML) usando Clean Architecture</title>
      <dc:creator>David Henry Bernardo Cardenas</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:43:40 +0000</pubDate>
      <link>https://dev.to/davidbernardo/como-construi-mi-portafolio-en-4-frameworks-react-19-vue-35-angular-19-y-compose-html-usando-3i4b</link>
      <guid>https://dev.to/davidbernardo/como-construi-mi-portafolio-en-4-frameworks-react-19-vue-35-angular-19-y-compose-html-usando-3i4b</guid>
      <description>&lt;p&gt;Como desarrollador &lt;strong&gt;Full Stack &amp;amp; Mobile Senior&lt;/strong&gt;, a menudo me encuentro con el mismo dilema en las entrevistas técnicas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Veo en tu CV que manejas React, Vue, Angular y Kotlin Multiplatform... ¿pero en cuál te sientes más cómodo?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cualquiera puede listar una docena de tecnologías en un PDF. Pero en el mundo del desarrollo de software, &lt;strong&gt;el código habla más fuerte que las palabras&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por eso me propuse un reto de ingeniería: en lugar de crear un portafolio tradicional en una sola tecnología, decidí &lt;strong&gt;construir una misma aplicación idéntica en los cuatro ecosistemas más potentes de la actualidad&lt;/strong&gt;, permitiendo al usuario cambiar de framework en caliente con un solo clic, sin perder el estado ni la posición de lectura:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;⚛️ &lt;strong&gt;React 19&lt;/strong&gt; (Vite + Hooks personalizados)&lt;/li&gt;
&lt;li&gt;💚 &lt;strong&gt;Vue 3.5&lt;/strong&gt; (Composition API + Composables)&lt;/li&gt;
&lt;li&gt;🅰️ &lt;strong&gt;Angular 19&lt;/strong&gt; (Standalone Components + Nuevas Signals)&lt;/li&gt;
&lt;li&gt;🟣 &lt;strong&gt;Kotlin Multiplatform / Compose HTML&lt;/strong&gt; (Kotlin compilado a JS/Web)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;¿La trampa? Si creas 4 proyectos separados, estás cuadruplicando el esfuerzo de mantenimiento: cada vez que actualizas un empleo, una habilidad o un proyecto, tendrías que modificar 4 bases de código distintas.&lt;/p&gt;

&lt;p&gt;Aquí es donde entra la verdadera protagonista de este proyecto: &lt;strong&gt;Clean Architecture (Arquitectura Limpia) en un Monorepo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5hp4rv7bymvda8gjxxef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5hp4rv7bymvda8gjxxef.png" alt="Portafolio con selector Dock de microfrontends" width="1899" height="1061"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🏛️ La Arquitectura: Separando la lógica del framework
&lt;/h2&gt;

&lt;p&gt;Para evitar duplicar código, la regla de oro fue: &lt;strong&gt;ningún framework debe ser dueño de la lógica ni de los datos&lt;/strong&gt;. Los frameworks son simplemente &lt;em&gt;mecanismos de entrega&lt;/em&gt; (presenters o adaptadores de entrada).&lt;/p&gt;

&lt;p&gt;Estructuré el proyecto en un monorepo con &lt;strong&gt;Yarn Workspaces&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── packages/
│   ├── core/                  # 🧠 Dominio puro (Entidades, Casos de Uso, Puertos)
│   ├── content-json-adapter/  # 🔌 Adaptador de infraestructura (i18n, lectura de JSON)
│   └── tokens/                # 🎨 Variables CSS de diseño y temas
├── apps/
│   ├── react-app/             # ⚛️ UI en React 19
│   ├── vue-app/               # 💚 UI en Vue 3.5
│   ├── angular-app/           # 🅰️ UI en Angular 19
│   └── compose-app/           # 🟣 UI en Kotlin Compose HTML
└── shell/                     # 🐚 Host orquestador y Dock Switcher
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. El Núcleo de Dominio (&lt;code&gt;packages/core&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Este paquete es &lt;strong&gt;TypeScript puro&lt;/strong&gt; y no tiene una sola línea de código acoplada al navegador, al DOM o a librerías externas. Contiene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entidades:&lt;/strong&gt; Modelos de dominio como &lt;code&gt;Experience&lt;/code&gt;, &lt;code&gt;Project&lt;/code&gt;, &lt;code&gt;Profile&lt;/code&gt;, &lt;code&gt;SkillCategory&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Puertos (Interfaces):&lt;/strong&gt; Define el contrato &lt;code&gt;ContentRepository&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ContentRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;getExperiences&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Experience&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nf"&gt;getProjects&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nf"&gt;getSkills&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;SkillCategory&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Casos de Uso:&lt;/strong&gt; Funciones puras con la lógica de negocio, por ejemplo &lt;code&gt;getExperienceTimeline&lt;/code&gt; o &lt;code&gt;filterProjectsByTech&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getExperienceTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContentRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SupportedLocale&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ExperienceVM&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getExperiences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;mapToExperienceVM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. El Adaptador de Datos (&lt;code&gt;packages/content-json-adapter&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Implementa el puerto &lt;code&gt;ContentRepository&lt;/code&gt; leyendo la fuente única de verdad (archivos JSON estructurados) y gestionando la traducción dinámica entre Español e Inglés.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚔️ La Comparativa: ¿Cómo consume cada framework el mismo caso de uso?
&lt;/h2&gt;

&lt;p&gt;Aquí viene la parte más fascinante: &lt;strong&gt;el consumo del caso de uso en cada tecnología&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Al estar la lógica en &lt;code&gt;packages/core&lt;/code&gt;, los componentes de la interfaz solo tienen una responsabilidad: &lt;em&gt;renderizar el ViewModel&lt;/em&gt;. Veamos cómo se implementa la reactividad en cada uno:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. ⚛️ React 19: Con Custom Hooks
&lt;/h3&gt;

&lt;p&gt;En React creamos un hook &lt;code&gt;usePortfolio&lt;/code&gt; que encapsula la suscripción al idioma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/react-app/src/hooks/usePortfolio.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;usePortfolio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useI18n&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonContentRepository&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getExperienceTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. 💚 Vue 3.5: Con Composition API y Composables
&lt;/h3&gt;

&lt;p&gt;En Vue aprovechamos la reactividad nativa con &lt;code&gt;computed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/vue-app/src/composables/usePortfolio.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;usePortfolio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useI18n&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonContentRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="nf"&gt;getExperienceTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. 🅰️ Angular 19: Con Signals Nativas
&lt;/h3&gt;

&lt;p&gt;Angular 19 ha revolucionado su reactividad abandonando la complejidad excesiva de RxJS en componentes para adoptar &lt;strong&gt;Signals&lt;/strong&gt;. Observa qué limpio queda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/angular-app/src/app/services/portfolio.service.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PortfolioService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;contentRepo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonContentRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;I18nService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Signal calculada automáticamente cuando cambia locale()&lt;/span&gt;
  &lt;span class="nx"&gt;experiences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="nf"&gt;getExperienceTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. 🟣 Kotlin Compose HTML: Declaratividad en la Web
&lt;/h3&gt;

&lt;p&gt;En Compose para Web utilizamos el paradigma declarativo de Jetpack Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// apps/compose-app/src/jsMain/kotlin/.../App.kt&lt;/span&gt;
&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PortfolioViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;locale&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;experiences&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getExperienceTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;ExperienceSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experiences&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;experiences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Lección clave:&lt;/strong&gt; Cada framework brilla a su manera, pero &lt;strong&gt;ninguno reinventa la rueda&lt;/strong&gt;. Si mañana decido añadir un nuevo empleo a mi trayectoria profesional, solo edito un archivo JSON y los 4 frameworks lo reflejan al instante con sus respectivos badges (&lt;code&gt;Full-time&lt;/code&gt; o &lt;code&gt;Freelance&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w14kske2851wcav8yp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w14kske2851wcav8yp4.png" alt="Línea de tiempo de Experiencia unificada" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 Sistema de Diseño Agnóstico: Vanilla CSS
&lt;/h2&gt;

&lt;p&gt;Uno de los errores comunes al hacer microfrontends es usar Tailwind o librerías pesadas (Material UI, Vuetify, PrimeNG) en cada aplicación, provocando colisiones de nombres de clases y una descarga masiva de megabytes innecesarios.&lt;/p&gt;

&lt;p&gt;La solución fue &lt;strong&gt;Vanilla CSS moderno con Tokens de Diseño compartidos&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se definieron variables CSS para colores, espaciados, fuentes y sombras (&lt;code&gt;--color-primary&lt;/code&gt;, &lt;code&gt;--color-surface&lt;/code&gt;, &lt;code&gt;--radius-lg&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Se implementaron temas &lt;strong&gt;Oscuro y Claro (Dark/Light Mode)&lt;/strong&gt; con &lt;code&gt;color-scheme&lt;/code&gt; y variables en el elemento raíz.&lt;/li&gt;
&lt;li&gt;Cada microfrontend utiliza exactamente las mismas clases semánticas (&lt;code&gt;.timeline-card&lt;/code&gt;, &lt;code&gt;.badge&lt;/code&gt;, &lt;code&gt;.project-card&lt;/code&gt;). El resultado es una &lt;strong&gt;paridad visual del 100%&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🐚 El Shell Host y el Switcher Dinámico
&lt;/h2&gt;

&lt;p&gt;¿Cómo orquestamos los 4 microfrontends en una sola página?&lt;/p&gt;

&lt;p&gt;Diseñé un &lt;strong&gt;Shell Host&lt;/strong&gt; ligero en TypeScript puro que:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Monta la aplicación seleccionada mediante un contenedor dinámico.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dock lateral interactivo&lt;/strong&gt; en pantallas de escritorio y &lt;strong&gt;Floating Action Button (FAB)&lt;/strong&gt; flotante al estilo Android en dispositivos móviles.&lt;/li&gt;
&lt;li&gt;Sincroniza el idioma actual (&lt;code&gt;es&lt;/code&gt; / &lt;code&gt;en&lt;/code&gt;), el tema visual y la posición de scroll para que la transición entre frameworks sea instantánea e imperceptible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9livt8dg71k0xxklnmy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9livt8dg71k0xxklnmy1.png" alt="Botón Flotante FAB en Mobile" width="752" height="1127"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🐳 Despliegue y Build con Docker y Nginx
&lt;/h2&gt;

&lt;p&gt;Compilar 4 frameworks con herramientas tan diversas (Vite, Angular CLI y el compilador de Kotlin/Gradle) puede ser un desafío en CI/CD.&lt;/p&gt;

&lt;p&gt;Para resolverlo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Usamos &lt;strong&gt;Docker&lt;/strong&gt; para garantizar un entorno reproducible con Node.js 22 y OpenJDK 17.&lt;/li&gt;
&lt;li&gt;Un script orquestador (&lt;code&gt;scripts/assemble-dist.mjs&lt;/code&gt;) compila cada aplicación y las ensambla en una estructura de producción limpia:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   dist/
   ├── index.html        # Shell host
   ├── react/            # Bundle de React 19
   ├── vue/              # Bundle de Vue 3.5
   ├── angular/          # Bundle de Angular 19
   └── compose/          # Bundle Wasm/JS de Compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Un contenedor &lt;strong&gt;Nginx&lt;/strong&gt; sirve los artefactos con compresión Gzip, cabeceras de caché inmutable para assets versionados y soporte de rutas SPA.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📊 Conclusiones y Aprendizajes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clean Architecture funciona de maravilla en Frontend:&lt;/strong&gt; A menudo se piensa que la arquitectura limpia es solo para backend. Desacoplar el dominio de la vista demostró ser la mejor decisión para mantener la coherencia del proyecto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular 19 con Signals es un placer:&lt;/strong&gt; La simplicidad de las Signals en Angular 19 lo acerca a la agilidad de Vue y React sin perder la robustez de TypeScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compose HTML / Kotlin Web es el futuro móvil/web unificado:&lt;/strong&gt; Escribir interfaces web con la misma sintaxis que uso en Android nativo permite una reutilización de habilidades sin precedentes.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🚀 Pruébalo en vivo y Código Fuente
&lt;/h2&gt;

&lt;p&gt;Puedes interactuar con el portafolio en vivo y cambiar de framework en tiempo real:&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Demo en vivo:&lt;/strong&gt; &lt;a href="https://www.davidbernardo.dev" rel="noopener noreferrer"&gt;davidbernardo.dev&lt;/a&gt;&lt;br&gt;&lt;br&gt;
⭐ &lt;strong&gt;Repositorio en GitHub:&lt;/strong&gt; &lt;a href="https://github.com/dhbernardo/portfolio" rel="noopener noreferrer"&gt;github.com/dhbernardo/portfolio&lt;/a&gt;&lt;br&gt;&lt;br&gt;
💼 &lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/david-henry-bernardo-cardenas" rel="noopener noreferrer"&gt;linkedin.com/in/david-henry-bernardo-cardenas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¿Qué framework de los cuatro prefieres tú para tus proyectos? ¡Déjame tu opinión en los comentarios!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>frontend</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
