El Problema: Gestión Manual y Falta de Presencia Digital
Peluquería Yaneth, un negocio local de Misiones (Argentina), gestionaba clientes exclusivamente por WhatsApp y papel. La dueña enfrentaba:
- ❌ Actualización manual de precios: Cada cambio requería editar múltiples archivos HTML
- ❌ Sin catálogos digitales: Los PDFs de productos (Natura, Avon, etc.) se compartían por WhatsApp individualmente
- ❌ Gestión de horarios caótica: Cambios estacionales (verano/invierno) implicaban rediseñar la web
- ❌ Cero analytics: No había forma de medir tráfico ni conversiones
Objetivo del proyecto: Crear un sistema headless CMS ligero donde el negocio pueda actualizar precios, horarios y catálogos sin tocar código.
Stack Tecnológico
Frontend
- Astro 5 - Framework estático con arquitectura de islas
- Tailwind CSS - Sistema de diseño personalizado ("Glam System")
- TypeScript - Type-safe data management
Backend & Infraestructura
- Cloudflare R2 - Object storage para PDFs de catálogos (25MB+)
- Cloudflare Workers - CDN edge computing para servir PDFs
- Vercel Edge Functions - Despliegue automático con CI/CD
Data Architecture
// src/data/siteData.ts - Single Source of Truth
export const siteData = {
info: { phone, email, address },
priceList: priceList, // Importado desde priceList.ts
catalogs: catalogsBank, // Gestión de catálogos activos/inactivos
schedule: [...], // Horarios dinámicos por temporada
promo: promotionsBank.find(p => p.active) || promotionsBank[0]
}
Analytics & SEO
- Google Analytics 4 + Umami (GDPR-compliant)
- Sitemap XML automático con
@astrojs/sitemap - OpenGraph dinámico para redes sociales
Desafíos de Ingeniería
1. Arquitectura de Datos Centralizada: El "Brain Pattern"
Problema: La cliente necesitaba cambiar precios/horarios sin editar 15 archivos diferentes.
Solución: Implementé un Single Source of Truth en TypeScript:
// src/data/priceList.ts
export const priceList = [
{
category: 'Cortes Damas',
items: [
{ name: 'Corte + Lavado', price: 5000 },
{ name: 'Corte Terminado', price: 6000 }
]
}
];
// Componente consume datos sin hardcodear nada
---
import { siteData } from '../data/siteData';
{siteData.priceList.map(category => (
<div>
<h3>{category.category}</h3>
{category.items.map(item => (
<p>{item.name}: ${item.price}</p>
))}
</div>
))}
---
Resultado: La dueña ahora edita un JSON simple y Vercel redeploya automáticamente en 2 minutos.
2. Sistema de Catálogos Digitales con PDF Hosting
Desafío: Los catálogos de Natura/Avon pesan 25MB+ y cambian cada mes. Google Drive era lento y requería múltiples clics.
Arquitectura:
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Astro App │─────▶│ Short.io │─────▶│ Cloudflare │
│ (CTA Link) │ │ (URL Corta) │ │ R2 + Worker │
└─────────────┘ └──────────────┘ └──────────────┘
Cloudflare Worker (Servir PDFs con caché optimizado):
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1); // "natura-c01.pdf"
const object = await env.BUCKET.get(key);
if (!object) return new Response("404 Not Found", { status: 404 });
const headers = new Headers();
headers.set("Content-Type", "application/pdf");
headers.set("Cache-Control", "public, max-age=2592000, immutable"); // 30 días
headers.set("Accept-Ranges", "bytes"); // Permite carga progresiva
return new Response(object.body, { headers });
},
};
Gestión dinámica en frontend:
// src/data/catalogs.ts
export const catalogsBank = [
{
brand: "Natura",
campaigns: [
{
name: "Ciclo 01/2025",
pdfUrl: "https://short.io/natura-c01",
active: true, // ← Toggle para mostrar/ocultar
},
],
},
];
// El componente solo renderiza catálogos activos
const activeCatalogs = catalogsBank
.map((brand) => ({
...brand,
campaigns: brand.campaigns.filter((c) => c.active),
}))
.filter((brand) => brand.campaigns.length > 0);
Impacto:
- ⚡ Primera carga: 8-12s en 4G (inevitable con 25MB)
- 🚀 Siguientes cargas: Instantáneo (caché del navegador)
- 📱 Carga progresiva: Safari/Chrome muestran páginas mientras descarga
3. SEO Local & Performance
Desafío: Posicionar en Google para búsquedas locales ("peluquería Cerro Azul").
Implementación:
---
// src/layouts/Layout.astro
const structuredData = {
"@context": "https://schema.org",
"@type": "HairSalon",
"name": "Peluquería Yaneth",
"address": {
"@type": "PostalAddress",
"streetAddress": "Tomas Guido 858",
"addressLocality": "Cerro Azul",
"addressRegion": "Misiones",
"postalCode": "3313",
"addressCountry": "AR"
},
"telephone": "+5493764986352",
"openingHours": "Mo-Sa 08:00-20:30"
};
---
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
Lighthouse Score:
- 🟢 Performance: 98/100
- 🟢 SEO: 100/100
- 🟢 Accessibility: 95/100
4. UX Mobile First: Gestión de Estado del Header
Problema: El menú hamburguesa debe cerrarse al navegar entre páginas (SPA).
Solución con View Transitions:
---
// src/components/organisms/Header.astro
---
<script>
// Cerrar menú al navegar (compatibilidad con ViewTransitions)
document.addEventListener('astro:before-swap', () => {
const checkbox = document.getElementById('menu-toggle') as HTMLInputElement;
if (checkbox) checkbox.checked = false;
});
</script>
Impacto en el Negocio
Métricas Cuantitativas
| Métrica | Antes | Después | Mejora |
|---|---|---|---|
| Tiempo de actualización de precios | 2 horas | 15 minutos | -87% |
| Envío de catálogos por WhatsApp | 50 mensajes/día | 0 (self-service) | -100% |
| Conversión web → WhatsApp | 0% (sin web) | 12% | +12% |
| Velocidad de carga (mobile) | N/A | 2.1s | ⚡ |
Resultados Cualitativos
- ✅ Autonomía del negocio: La dueña actualiza todo sin depender del desarrollador
- ✅ Profesionalización: De WhatsApp a una presencia digital moderna
- ✅ Analytics: Umami + GA4 revelan que el 68% del tráfico es mobile (justifica diseño mobile-first)
Arquitectura de Componentes: Atomic Design
/components
├── atoms/ # Button, Heading, MapFrame
├── molecules/ # BlogCard, ServiceCard
└── organisms/ # Header, Footer, Hero, PriceTable
// Ejemplo de reusabilidad
<Button href={siteData.info.whatsappLink} variant="primary">
Reservar Turno
</Button>
Conclusión: Escalabilidad y Lecciones Aprendidas
Lo que Funcionó
- TypeScript + Data Centralization = Cambios sin miedo
- Cloudflare R2 + Workers = PDFs pesados con carga rápida
- Astro Static = SEO perfecto + costos $0 de servidor
Próximos Pasos
- 🔜 Sistema de reservas: Integración con Calendly o API custom
- 🔜 Blog automático: Content Collections para tips de belleza
- 🔜 E-commerce: Venta de productos de catálogo
Code Quality
# ESLint + Prettier configurados
npm run lint:fix
npm run format
# CI/CD con GitHub Actions (auto-deploy en merge a main)
🔗 Enlaces del Proyecto
- Sitio en Producción: peluqueriayaneth.vercel.app
- Repositorio GitHub: github.com/LeoMartinK/peluqueria-yaneth (privado)
- Portafolio del Desarrollador: leogaleano.vercel.app
- Template Showcase: github.com/LeoMartinK/beauty-salon-astro-template
Tech Stack Summary: Astro + TypeScript + Tailwind CSS + Cloudflare R2/Workers + Vercel Edge + Google Analytics 4 + Umami
Este proyecto demuestra cómo frameworks modernos (JAMstack) pueden transformar negocios locales sin requerir infraestructura compleja ni costos de hosting.