diff --git a/apps/portfolio/app/[locale]/projects/[slug]/page.tsx b/apps/portfolio/app/[locale]/projects/[slug]/page.tsx index 00f06c2..572a0f7 100644 --- a/apps/portfolio/app/[locale]/projects/[slug]/page.tsx +++ b/apps/portfolio/app/[locale]/projects/[slug]/page.tsx @@ -3,6 +3,10 @@ import { notFound } from 'next/navigation'; import { getTranslations } from 'next-intl/server'; import { Project, type ProjectData } from '@/core/entities/Project'; +import { + getProjectDetail, + type ProjectDetailContent, +} from '@/features/projects/utils/projectDetail'; import { routing } from '@/i18n/routing'; import { PROJECT_SLUGS } from '@/shared/constants/project-slugs'; @@ -36,6 +40,8 @@ export default async function ProjectDetailPage({ params }: ProjectDetailPagePro } const project = new Project(projectData); + const details = t.raw('details') as Record | undefined; + const detail = getProjectDetail(details, slug); return (
@@ -49,32 +55,72 @@ export default async function ProjectDetailPage({ params }: ProjectDetailPagePro

- Detalle en preparación + {detail ? project.categoryId.replace(/-/g, ' ') : 'Detalle en preparación'}

{project.title}

{project.description}

-
-

- Estamos preparando una ficha completa con arquitectura, capturas y métricas clave de - este proyecto. Mientras tanto, puedes solicitar una demo privada o más información - directa. -

-
- - {project.categoryId.replace(/-/g, ' ')} - - {project.tech.map((tech) => ( - - {tech} + {detail ? ( + <> + {detail.metrics && detail.metrics.length > 0 && ( +
+ {detail.metrics.map((metric) => ( +
+

{metric.value}

+

+ {metric.label} +

+
+ ))} +
+ )} + +
+ {detail.sections.map((item) => ( +
+

{item.heading}

+

{item.body}

+
+ ))} +
+ +
+ {project.tech.map((tech) => ( + + {tech} + + ))} +
+ + ) : ( +
+

+ Estamos preparando una ficha completa con arquitectura, capturas y métricas clave de + este proyecto. Mientras tanto, puedes solicitar una demo privada o más información + directa. +

+
+ + {project.categoryId.replace(/-/g, ' ')} - ))} -
-
+ {project.tech.map((tech) => ( + + {tech} + + ))} +
+
+ )}

diff --git a/apps/portfolio/messages/en.json b/apps/portfolio/messages/en.json index 5a6f5c2..9229ade 100644 --- a/apps/portfolio/messages/en.json +++ b/apps/portfolio/messages/en.json @@ -105,14 +105,17 @@ "career": { "experiences": [ { - "role": "Consultant & Tech Lead", + "role": "Full-Stack Developer & Tech Lead", "company": "Fastbyte SL", "period": { "start": "2019", "end": "2025" }, - "headline": "Requirements gathering with clients/teams and translation into practical solutions.", - "achievements": ["Constant communication, task follow-up and phased delivery."] + "headline": "Maintenance of a production ERP for a technical-services company and the design and implementation of its full-stack migration (Symfony/API Platform + Next.js).", + "achievements": [ + "Full-stack migration: a Symfony 7.3 + API Platform REST API and a Next.js 15 SPA; paused by the client's business decision.", + "Incremental modernization of the legacy PHP system (Repository pattern, DI, Twilio/WhatsApp, DomPDF)." + ] }, { "role": "Program Specialist", @@ -125,14 +128,17 @@ "achievements": [] }, { - "role": "Co-founder & Lead Dev", - "company": "GestionoMiNegocio", + "role": "Co-founder & Lead Developer", + "company": "GestionoMiNegocio (GMN)", "period": { "start": "2020", - "end": "2025" + "end": "Present" }, - "headline": "Working with beta users: collecting feedback, improvements and product onboarding support.", - "achievements": ["Focus on clarity, quality and user experience."] + "headline": "Multi-tenant electronic-invoicing SaaS platform with Veri*factu/AEAT compliance; distributed architecture with a compliance microservice.", + "achievements": [ + "Veri*factu/AEAT microservice: SHA-256 hash chaining, XAdES signatures and SOAP/mTLS (Symfony Messenger).", + "Database-per-tenant multi-tenancy and SaaS monetization with Stripe." + ] }, { "role": "Developer", @@ -506,6 +512,68 @@ } }, "projects": { + "details": { + "gestiono-mi-negocio-facturacion": { + "sections": [ + { + "heading": "Context", + "body": "GestionoMiNegocio (GMN) is a multi-tenant SaaS platform for business management and electronic invoicing for freelancers and SMEs, compliant with Spain's Veri*factu/AEAT regulation. Live in production with real businesses. As co-founder and Lead Dev (2020–present) I led its evolution from a legacy management system to the current distributed architecture." + }, + { + "heading": "Multi-tenant architecture", + "body": "A management app (invoices, quotes, delivery notes and clients) on a database-per-tenant model with full data isolation, dynamic connection switching and automated tenant provisioning/migration. Built with Symfony 7, PHP 8.2, Doctrine ORM, MariaDB, Twig and Bootstrap 5." + }, + { + "heading": "Veri*factu/AEAT compliance microservice", + "body": "A standalone service that handles tax registration with the Spanish Tax Agency (Anti-Fraud Law, RD 1007/2023): SHA-256 hash chaining, XAdES-EPES digital signatures with FNMT certificates (PKCS#12), QR generation and SOAP/mTLS communication, processed asynchronously via Symfony Messenger." + }, + { + "heading": "Monetization and product", + "body": "A SaaS monetization layer with Stripe (plans, trials, idempotent webhooks and plan-based feature gating), self-service onboarding and integrations with TwentyCRM and an OpenAI assistant. Full product cycle with beta users: feedback, improvements and support." + }, + { + "heading": "Engineering", + "body": "Docker containerization, CI on GitHub Actions and a REST API. ~31,000 lines of code across the GMN app and the compliance microservice, which has 33 REST endpoints and 27 test files." + } + ], + "metrics": [ + { "label": "LOC (app + microservice)", "value": "~31k" }, + { "label": "Microservice endpoints", "value": "33" }, + { "label": "Compliance", "value": "Veri*factu/AEAT" }, + { "label": "Data model", "value": "Database-per-tenant" } + ] + }, + "fastbyte-api-servicios": { + "sections": [ + { + "heading": "Context", + "body": "A technical-services company with its own in-house ERP (\"Genesis\") running in production: ~16 modules and ~118,000 lines of code covering repair orders, incidents, quotes, delivery notes, billing and clients." + }, + { + "heading": "Legacy maintenance and modernization", + "body": "A hybrid PHP 7.4 system (procedural + OOP). Production maintenance and incremental modernization: Repository pattern and dependency injection in the newer modules, Twig templates, PDF generation (FPDF → DomPDF) and WhatsApp notifications via Twilio. Team work with a pull-request workflow." + }, + { + "heading": "Full-stack migration", + "body": "Design and implementation of the decoupled rewrite. Backend: Symfony 7.3 + API Platform 4.1 with the State Provider/Processor pattern, custom normalizers, a sequential ID generator with locking, and JSON-LD/Hydra serialization. Frontend: Next.js 15 + React 19 + TypeScript with shadcn/ui, React Hook Form + Zod (schema-first validation) and TanStack Table." + }, + { + "heading": "Quality and architecture", + "body": "Decisions documented in ADRs, ~24 backend tests (API, entities, serialization) and 300+ frontend tests with MSW for API mocking, plus continuous integration and documentation (OpenAPI, CHANGELOG)." + }, + { + "heading": "Outcome", + "body": "The migration was built as a standalone project and paused by the client's business decision; the legacy ERP remains in production." + } + ], + "metrics": [ + { "label": "API endpoints", "value": "~34" }, + { "label": "Domain entities", "value": "16" }, + { "label": "Tests (front + back)", "value": "320+" }, + { "label": "Stack", "value": "Symfony · Next.js" } + ] + } + }, "title": "Projects", "subtitle": "Selection of SaaS products, internal tools and technical documentation created in professional projects. Organized into 6 strategic categories.", "filters": { @@ -568,16 +636,23 @@ "items": [ { "id": "gestiono-mi-negocio-facturacion", - "title": "GestionoMiNegocio - Billing Software", - "description": "Led development as Co-founder & Lead Dev. Comprehensive platform for basic administrative management, including billing and collection control. Architecture oriented to handle workload peaks while maintaining quality and response times.", + "title": "GestionoMiNegocio (GMN) — Electronic invoicing SaaS platform", + "description": "Multi-tenant SaaS platform for business management and electronic invoicing for freelancers and SMEs, fully compliant with Spain's Veri*factu / AEAT regulation. Distributed architecture: a management app (invoices, quotes, delivery notes and clients) on a database-per-tenant model, plus a standalone microservice that handles tax registration with the Spanish Tax Agency (hash chaining, XAdES digital signatures and SOAP communication). Includes Stripe subscription billing, plan-based feature gating and self-service onboarding. Live in production with real businesses.", "tech": [ "Symfony 7", - "API Platform", - "PostgreSQL", - "React", - "TypeScript", + "PHP 8.2", + "Doctrine ORM", + "MariaDB", + "Twig", + "Bootstrap 5", + "REST API", + "Stripe", + "Symfony Messenger", "Docker", - "GitHub Actions" + "GitHub Actions", + "Veri*factu/AEAT", + "XAdES", + "Multi-tenant" ], "links": { "demo": "https://gestionominegocio.com/" @@ -605,9 +680,18 @@ }, { "id": "fastbyte-api-servicios", - "title": "Fastbyte - Service Management REST API", - "description": "Consultant and Tech Lead focused on requirement gathering with clients and translating them into practical solutions. Robust REST API for service management with phased deliveries and constant client communication.", - "tech": ["Symfony 7.3", "API Platform", "MariaDB", "Docker"], + "title": "Fastbyte — Technical-Services ERP Modernization", + "description": "Maintenance and evolution of the production ERP of a technical-services company (repair orders, incidents and billing), plus the design and implementation of its full-stack migration to a modern architecture: a Symfony 7.3 + API Platform REST API and a Next.js 15 SPA. The migration was built as a standalone project and paused by the client's business decision.", + "tech": [ + "Symfony 7.3", + "API Platform", + "Next.js 15", + "React 19", + "TypeScript", + "Doctrine ORM", + "MariaDB", + "Docker" + ], "links": {}, "categoryId": "gestion-servicios" }, diff --git a/apps/portfolio/messages/es.json b/apps/portfolio/messages/es.json index ca896fc..ffdc21c 100644 --- a/apps/portfolio/messages/es.json +++ b/apps/portfolio/messages/es.json @@ -105,14 +105,17 @@ "career": { "experiences": [ { - "role": "Consultor & Tech Lead", + "role": "Desarrollador Full-Stack & Tech Lead", "company": "Fastbyte SL", "period": { "start": "2019", "end": "2025" }, - "headline": "Toma de requisitos con clientes/equipos y traducción a soluciones prácticas.", - "achievements": ["Comunicación constante, seguimiento de tareas y entrega por fases."] + "headline": "Mantenimiento de un ERP en producción para servicios técnicos y diseño e implementación de su migración full-stack (Symfony/API Platform + Next.js).", + "achievements": [ + "Migración full-stack: API REST con Symfony 7.3 + API Platform y SPA en Next.js 15; pausada por decisión de negocio del cliente.", + "Modernización incremental del legacy PHP (Repository pattern, DI, Twilio/WhatsApp, DomPDF)." + ] }, { "role": "Especialista de Programa", @@ -125,14 +128,17 @@ "achievements": [] }, { - "role": "Cofundador & Lead Dev", - "company": "GestionoMiNegocio", + "role": "Cofundador & Lead Developer", + "company": "GestionoMiNegocio (GMN)", "period": { "start": "2020", - "end": "2025" + "end": "Actualidad" }, - "headline": "Trabajo con usuarios beta: recogida de feedback, mejoras y acompañamiento en uso del producto.", - "achievements": ["Enfoque en claridad, calidad y experiencia de usuario."] + "headline": "Plataforma SaaS multi-tenant de facturación electrónica con cumplimiento Veri*factu/AEAT; arquitectura distribuida con microservicio de compliance.", + "achievements": [ + "Microservicio Veri*factu/AEAT: hash encadenado SHA-256, firma XAdES y SOAP/mTLS (Symfony Messenger).", + "Multi-tenancy database-per-tenant y monetización SaaS con Stripe." + ] }, { "role": "Developer", @@ -506,6 +512,68 @@ } }, "projects": { + "details": { + "gestiono-mi-negocio-facturacion": { + "sections": [ + { + "heading": "Contexto", + "body": "GestionoMiNegocio (GMN) es una plataforma SaaS multi-tenant de gestión y facturación electrónica para autónomos y pymes, con cumplimiento de la normativa española Veri*factu/AEAT. En producción con empresas reales. Como cofundador y Lead Dev (2020–actualidad) lideré su evolución desde un software de gestión legacy hasta la arquitectura distribuida actual." + }, + { + "heading": "Arquitectura multi-tenant", + "body": "Aplicación de gestión (facturas, presupuestos, albaranes y clientes) sobre un modelo database-per-tenant con aislamiento total de datos, conmutación dinámica de conexión y provisión/migración automatizada de tenants. Construida con Symfony 7, PHP 8.2, Doctrine ORM, MariaDB, Twig y Bootstrap 5." + }, + { + "heading": "Microservicio de compliance Veri*factu/AEAT", + "body": "Servicio independiente que resuelve el registro fiscal ante la AEAT (Ley Antifraude, RD 1007/2023): hash encadenado SHA-256, firma digital XAdES-EPES con certificados FNMT (PKCS#12), generación de QR y comunicación SOAP con mTLS, procesado de forma asíncrona con Symfony Messenger." + }, + { + "heading": "Monetización y producto", + "body": "Capa de monetización SaaS con Stripe (planes, periodo de prueba, webhooks idempotentes y feature-gating por plan), onboarding self-service e integraciones con TwentyCRM y un asistente con OpenAI. Ciclo completo de producto con usuarios beta: feedback, mejoras y soporte." + }, + { + "heading": "Ingeniería", + "body": "Contenedorización con Docker, CI con GitHub Actions y API REST. ~31.000 líneas de código entre la app GMN y el microservicio de compliance, que cuenta con 33 endpoints REST y 27 ficheros de test." + } + ], + "metrics": [ + { "label": "LOC (app + microservicio)", "value": "~31k" }, + { "label": "Endpoints microservicio", "value": "33" }, + { "label": "Compliance", "value": "Veri*factu/AEAT" }, + { "label": "Modelo de datos", "value": "Database-per-tenant" } + ] + }, + "fastbyte-api-servicios": { + "sections": [ + { + "heading": "Contexto", + "body": "Empresa de servicios técnicos con un ERP propio (\"Genesis\") en producción: ~16 módulos y ~118.000 líneas de código que cubren órdenes de reparación, incidencias, presupuestos, albaranes, facturación y clientes." + }, + { + "heading": "Mantenimiento y modernización del legacy", + "body": "Sistema PHP 7.4 de arquitectura híbrida (procedural + OOP). Mantenimiento en producción y modernización incremental: Repository pattern e inyección de dependencias en los módulos nuevos, plantillas Twig, generación de PDF (FPDF → DomPDF) y notificaciones por WhatsApp vía Twilio. Trabajo en equipo con flujo de pull requests." + }, + { + "heading": "Migración full-stack", + "body": "Diseño e implementación de la reescritura desacoplada. Backend: Symfony 7.3 + API Platform 4.1 con patrón State Provider/Processor, normalizers a medida, generador secuencial de IDs con bloqueo y serialización JSON-LD/Hydra. Frontend: Next.js 15 + React 19 + TypeScript con shadcn/ui, React Hook Form + Zod (validación schema-first) y TanStack Table." + }, + { + "heading": "Calidad y arquitectura", + "body": "Decisiones documentadas en ADRs, ~24 tests en el backend (API, entidades, serialización) y 300+ tests en el frontend con MSW para el mocking de la API, además de integración continua y documentación (OpenAPI, CHANGELOG)." + }, + { + "heading": "Desenlace", + "body": "La migración se desarrolló como proyecto independiente y se pausó por decisión de negocio del cliente; el ERP legacy continúa en producción." + } + ], + "metrics": [ + { "label": "Endpoints API", "value": "~34" }, + { "label": "Entidades de dominio", "value": "16" }, + { "label": "Tests (front + back)", "value": "320+" }, + { "label": "Stack", "value": "Symfony · Next.js" } + ] + } + }, "title": "Proyectos", "subtitle": "Selección de productos SaaS, herramientas internas y documentación técnica desarrollada en proyectos profesionales. Organizados en 6 categorías estratégicas.", "filters": { @@ -568,16 +636,23 @@ "items": [ { "id": "gestiono-mi-negocio-facturacion", - "title": "GestionoMiNegocio - Software de Facturación", - "description": "Desarrollo liderado como Cofundador y Lead Dev. Plataforma integral para la gestión administrativa básica, facturación y control de cobros. Arquitectura orientada a resolver picos de trabajo manteniendo calidad y tiempos de respuesta.", + "title": "GestionoMiNegocio (GMN) — Plataforma SaaS de facturación electrónica", + "description": "Plataforma SaaS multi-tenant para la gestión y la facturación electrónica de autónomos y pymes, con cumplimiento de la normativa española Veri*factu (AEAT). Arquitectura distribuida: una aplicación de gestión (facturas, presupuestos, albaranes y clientes) sobre un modelo database-per-tenant, y un microservicio independiente que resuelve el registro fiscal ante la AEAT (hash encadenado, firma digital XAdES y comunicación SOAP). Incluye suscripciones y cobros con Stripe, control de planes por funcionalidad y onboarding self-service. En producción con empresas reales.", "tech": [ "Symfony 7", - "API Platform", - "PostgreSQL", - "React", - "TypeScript", + "PHP 8.2", + "Doctrine ORM", + "MariaDB", + "Twig", + "Bootstrap 5", + "API REST", + "Stripe", + "Symfony Messenger", "Docker", - "GitHub Actions" + "GitHub Actions", + "Veri*factu/AEAT", + "XAdES", + "Multi-tenant" ], "links": { "demo": "https://gestionominegocio.com/" @@ -605,9 +680,18 @@ }, { "id": "fastbyte-api-servicios", - "title": "Fastbyte - API REST de Gestión de Servicios", - "description": "Consultor y Tech Lead en toma de requisitos con clientes y su traducción a soluciones prácticas. API REST robusta para gestión de servicios con entregas por fases y comunicación constante con el cliente.", - "tech": ["Symfony 7.3", "API Platform", "MariaDB", "Docker"], + "title": "Fastbyte — Modernización de ERP de Servicios Técnicos", + "description": "Mantenimiento y evolución del ERP en producción de una empresa de servicios técnicos (órdenes de reparación, incidencias y facturación), y diseño e implementación de su migración full-stack a una arquitectura moderna: API REST con Symfony 7.3 + API Platform y SPA en Next.js 15. La migración se desarrolló como proyecto independiente y se pausó por decisión de negocio del cliente.", + "tech": [ + "Symfony 7.3", + "API Platform", + "Next.js 15", + "React 19", + "TypeScript", + "Doctrine ORM", + "MariaDB", + "Docker" + ], "links": {}, "categoryId": "gestion-servicios" }, diff --git a/apps/portfolio/src/features/projects/utils/projectDetail.test.ts b/apps/portfolio/src/features/projects/utils/projectDetail.test.ts new file mode 100644 index 0000000..4eec62b --- /dev/null +++ b/apps/portfolio/src/features/projects/utils/projectDetail.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; + +import { getProjectDetail, type ProjectDetailContent } from './projectDetail'; + +describe('getProjectDetail', () => { + const details: Record = { + 'fastbyte-api-servicios': { + sections: [{ heading: 'Contexto', body: 'ERP en producción.' }], + metrics: [{ label: 'Endpoints API', value: '~34' }], + }, + }; + + it('returns the detail content when the slug exists', () => { + expect(getProjectDetail(details, 'fastbyte-api-servicios')).toEqual( + details['fastbyte-api-servicios'] + ); + }); + + it('returns null when the slug has no detail content', () => { + expect(getProjectDetail(details, 'seedstockers-b2b-platform')).toBeNull(); + }); + + it('returns null when details is undefined', () => { + expect(getProjectDetail(undefined, 'fastbyte-api-servicios')).toBeNull(); + }); +}); diff --git a/apps/portfolio/src/features/projects/utils/projectDetail.ts b/apps/portfolio/src/features/projects/utils/projectDetail.ts new file mode 100644 index 0000000..8864c0d --- /dev/null +++ b/apps/portfolio/src/features/projects/utils/projectDetail.ts @@ -0,0 +1,27 @@ +/** + * Tipos y selector del contenido de la página de detalle de un proyecto. + * El contenido vive en los ficheros i18n (messages/*.json) bajo `projects.details`, + * indexado por slug. `getProjectDetail` es puro para poder testearlo sin next-intl. + */ + +export interface ProjectDetailSection { + heading: string; + body: string; +} + +export interface ProjectDetailMetric { + label: string; + value: string; +} + +export interface ProjectDetailContent { + sections: ProjectDetailSection[]; + metrics?: ProjectDetailMetric[]; +} + +export function getProjectDetail( + details: Record | undefined, + slug: string +): ProjectDetailContent | null { + return details?.[slug] ?? null; +}