Initial commit: Vite + React project setup

This commit is contained in:
Sebastian
2025-12-05 16:56:43 +00:00
commit bd25742bf5
32 changed files with 3369 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

162
README.md Normal file
View File

@@ -0,0 +1,162 @@
# Maya Contigo - Banorte Interface
Aplicación web moderna desarrollada con **Vite + React + TailwindCSS** que replica el diseño de la interfaz "Maya Contigo" de Banorte.
## Características
- **Header fijo** con branding de Banorte
- **Hero Banner** con efectos de iluminación circular roja
- **Carrusel configurable** con navegación suave y animaciones
- **Diseño responsivo** para móvil, tablet y desktop
- **Sin librerías externas** para el carrusel (implementación custom)
- **Fácil de extender** - solo edita el archivo de datos
## Tecnologías
-**Vite** - Build tool rápido
- ⚛️ **React 18** - UI Library
- 🎨 **TailwindCSS** - Utility-first CSS framework
- 🎯 **CSS Custom** - Animaciones y transiciones suaves
## Instalación
1. **Clonar e instalar dependencias:**
```bash
npm install
```
2. **Iniciar servidor de desarrollo:**
```bash
npm run dev
```
3. **Abrir en el navegador:**
El servidor se iniciará en `http://localhost:5173`
## Estructura del Proyecto
```
myinterfaz/
├── public/
│ └── images/ # Imágenes de los módulos
│ ├── rh.png
│ ├── normativa.png
│ ├── cx.png
│ ├── bursatil.png
│ └── pyme.png
├── src/
│ ├── components/
│ │ ├── Header.jsx # Header fijo con logo
│ │ ├── Hero.jsx # Banner principal
│ │ └── Carousel.jsx # Carrusel configurable
│ ├── data/
│ │ └── modules.js # Datos de los módulos
│ ├── App.jsx # Componente principal
│ ├── main.jsx # Entry point
│ └── index.css # Estilos globales
├── index.html
├── vite.config.js
├── tailwind.config.js
└── package.json
```
## Cómo Agregar Nuevos Módulos
Es muy fácil agregar nuevos módulos al carrusel. Solo necesitas editar el archivo `src/data/modules.js`:
```javascript
export const modules = [
// Módulos existentes...
{
id: 6, // ID único
title: "MAYA CONTIGO", // Título superior (azul)
subtitle: "NUEVO MODULO", // Subtítulo (rojo)
img: "/images/nuevo-modulo.png", // Ruta de la imagen
url: "https://example.com/nuevo" // URL de destino
}
];
```
### Pasos para agregar un módulo:
1. **Agregar la imagen** en `public/images/`
2. **Editar** `src/data/modules.js`
3. **Agregar un nuevo objeto** al array con los campos requeridos
4. ¡Listo! El carrusel se actualizará automáticamente
## Colores de Marca Banorte
Los colores están configurados en `tailwind.config.js`:
- **Rojo Banorte:** `#C8102E` (usa `bg-banorte-red` o `text-banorte-red`)
- **Azul Banorte:** `#001A4D` (usa `bg-banorte-blue` o `text-banorte-blue`)
## Personalización
### Cambiar el tamaño del carrusel
Edita las clases de tamaño en `src/components/Carousel.jsx`:
```jsx
// Busca estas clases en el estilo inline
.active-item .circle-container {
width: 240px; // Tamaño del círculo activo
height: 240px;
}
```
### Modificar velocidad de animación
Cambia la duración de la transición:
```jsx
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
// Cambia 0.5s por el valor deseado
```
## Comandos Disponibles
```bash
# Desarrollo
npm run dev
# Build para producción
npm run build
# Preview del build
npm run preview
```
## Características del Carrusel
-**Navegación con flechas** izquierda/derecha
-**Elemento central ampliado** con efecto de enfoque
-**Animaciones suaves** entre transiciones
-**Responsive** - se adapta a diferentes pantallas
-**Click para navegar** - click en el elemento activo abre el URL
-**Escala y opacidad** para elementos no activos
-**Sin librerías externas** - implementación 100% custom
## Responsive
- **Móvil (< 768px):** Muestra 3 elementos (1 central + 2 laterales)
- **Tablet (768-1024px):** Muestra 3-5 elementos
- **Desktop (> 1024px):** Muestra 5 elementos completos
## Soporte de Navegadores
- Chrome (últimas 2 versiones)
- Firefox (últimas 2 versiones)
- Safari (últimas 2 versiones)
- Edge (últimas 2 versiones)
## Licencia
Proyecto educativo - Banorte Maya Contigo Interface
---
**Desarrollado con ❤️ usando Vite + React + TailwindCSS**

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="public/images/brujula.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Maya Contigo - Banorte</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2577
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "maya-contigo-interface",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"vite": "^5.4.11"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/images/brujula.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,11 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad4" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#C8102E;stop-opacity:1" />
<stop offset="100%" style="stop-color:#001A4D;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="400" height="400" fill="url(#grad4)"/>
<text x="200" y="180" font-family="Arial, sans-serif" font-size="55" font-weight="bold" fill="white" text-anchor="middle">BURSA</text>
<text x="200" y="250" font-family="Arial, sans-serif" font-size="55" font-weight="bold" fill="white" text-anchor="middle">TIL</text>
</svg>

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

10
public/images/cx.png Normal file
View File

@@ -0,0 +1,10 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad3" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#001A4D;stop-opacity:1" />
<stop offset="100%" style="stop-color:#C8102E;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="400" height="400" fill="url(#grad3)"/>
<text x="200" y="220" font-family="Arial, sans-serif" font-size="100" font-weight="bold" fill="white" text-anchor="middle">CX</text>
</svg>

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
public/images/grcas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/images/hero_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
public/images/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
public/images/normativa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/images/pruebas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

10
public/images/pyme.png Normal file
View File

@@ -0,0 +1,10 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad5" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#001A4D;stop-opacity:1" />
<stop offset="100%" style="stop-color:#C8102E;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="400" height="400" fill="url(#grad5)"/>
<text x="200" y="220" font-family="Arial, sans-serif" font-size="80" font-weight="bold" fill="white" text-anchor="middle">PYME</text>
</svg>

After

Width:  |  Height:  |  Size: 512 B

BIN
public/images/rh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
public/images/riesgos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

28
src/App.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React from 'react';
import Header from './components/Header';
import Hero from './components/Hero';
import Carousel from './components/Carousel';
import { modules } from './data/modules';
function App() {
return (
<div
className="min-h-screen bg-gray-50"
style={{
backgroundImage: 'url(/images/grcas.png)',
backgroundRepeat: 'repeat',
backgroundSize: 'auto',
backgroundAttachment: 'fixed'
}}
>
<Header />
<main>
<Hero />
<Carousel items={modules} />
</main>
<footer className="w-full h-8 bg-banorte-red mt-auto"></footer>
</div>
);
}
export default App;

194
src/components/Carousel.css Normal file
View File

@@ -0,0 +1,194 @@
.carousel-item {
position: absolute;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.circle-container {
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.circle-inner {
border-radius: 50%;
background: white;
padding: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.circle-inner::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
background: rgba(0, 0, 0, 0.8);
transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1;
pointer-events: none;
}
/* Active (Center) Item */
.active-item {
transform: translateX(0) scale(1);
z-index: 20;
opacity: 1;
}
.active-item .circle-container {
width: 240px;
height: 240px;
}
.active-item .circle-inner {
box-shadow: 0 20px 40px rgba(200, 16, 46, 0.4);
}
.active-item .circle-inner::before {
opacity: 0;
}
.active-item .item-title {
font-size: 1.125rem;
}
.active-item .item-subtitle {
font-size: 1.5rem;
}
/* Left Item */
.left-item {
transform: translateX(-380px) scale(0.75);
z-index: 10;
opacity: 0.6;
}
.left-item .circle-container {
width: 180px;
height: 180px;
}
.left-item .item-title {
font-size: 0.875rem;
}
.left-item .item-subtitle {
font-size: 1.125rem;
}
/* Right Item */
.right-item {
transform: translateX(380px) scale(0.75);
z-index: 10;
opacity: 0.6;
}
.right-item .circle-container {
width: 180px;
height: 180px;
}
.right-item .item-title {
font-size: 0.875rem;
}
.right-item .item-subtitle {
font-size: 1.125rem;
}
/* Far Left Item */
.far-left-item {
transform: translateX(-650px) scale(0.6);
z-index: 5;
opacity: 0.3;
}
.far-left-item .circle-container {
width: 150px;
height: 150px;
}
.far-left-item .item-title {
font-size: 0.75rem;
}
.far-left-item .item-subtitle {
font-size: 1rem;
}
/* Far Right Item */
.far-right-item {
transform: translateX(650px) scale(0.6);
z-index: 5;
opacity: 0.3;
}
.far-right-item .circle-container {
width: 150px;
height: 150px;
}
.far-right-item .item-title {
font-size: 0.75rem;
}
.far-right-item .item-subtitle {
font-size: 1rem;
}
/* Hidden Items */
.hidden-item {
transform: translateX(0) scale(0.3);
z-index: 0;
opacity: 0;
pointer-events: none;
}
.hidden-item .circle-container {
width: 120px;
height: 120px;
}
/* Hover Effects */
.active-item:hover .circle-inner {
box-shadow: 0 24px 48px rgba(200, 16, 46, 0.5);
transform: translateY(-4px);
}
/* Responsive Design */
@media (max-width: 768px) {
.active-item .circle-container {
width: 200px;
height: 200px;
}
.left-item,
.right-item {
transform: translateX(0) scale(0.5);
opacity: 0.3;
}
.left-item {
transform: translateX(-160px) scale(0.5);
}
.right-item {
transform: translateX(160px) scale(0.5);
}
.left-item .circle-container,
.right-item .circle-container {
width: 140px;
height: 140px;
}
.far-left-item,
.far-right-item,
.hidden-item {
opacity: 0;
pointer-events: none;
}
}

131
src/components/Carousel.jsx Normal file
View File

@@ -0,0 +1,131 @@
import React, { useState } from 'react';
import './Carousel.css';
const Carousel = ({ items }) => {
const [activeIndex, setActiveIndex] = useState(2); // Start with middle item
const handleNext = () => {
setActiveIndex((prev) => (prev + 1) % items.length);
};
const handlePrev = () => {
setActiveIndex((prev) => (prev - 1 + items.length) % items.length);
};
const handleItemClick = (url) => {
window.location.href = url;
};
// Function to get the position class for each item
const getPositionClass = (index) => {
const diff = index - activeIndex;
const length = items.length;
// Normalize difference to handle circular array
let normalizedDiff = diff;
if (Math.abs(diff) > length / 2) {
normalizedDiff = diff > 0 ? diff - length : diff + length;
}
if (normalizedDiff === 0) {
return 'active-item'; // Center
} else if (normalizedDiff === -1) {
return 'left-item'; // Immediate left
} else if (normalizedDiff === 1) {
return 'right-item'; // Immediate right
} else if (normalizedDiff === -2) {
return 'far-left-item'; // Far left
} else if (normalizedDiff === 2) {
return 'far-right-item'; // Far right
} else {
return 'hidden-item'; // Hidden
}
};
return (
<section className="relative py-4 px-4 bg-white">
<div className="container mx-auto">
{/* Carousel Container */}
<div className="relative flex items-center justify-center min-h-[380px]">
{/* Carousel Items */}
<div className="relative w-full max-w-6xl h-[400px] flex items-center justify-center">
{/* Left Arrow - positioned next to center item */}
<button
onClick={handlePrev}
className="absolute left-1/2 -translate-x-[220px] z-30 transition-all duration-200 hover:scale-110"
aria-label="Previous"
>
<img
src="/images/flecha-izq.png"
alt="Previous"
className="w-12 h-12"
/>
</button>
{items.map((item, index) => {
const positionClass = getPositionClass(index);
return (
<div
key={item.id}
className={`carousel-item ${positionClass}`}
onClick={() => activeIndex === index && handleItemClick(item.url)}
>
{/* Circular Image Container */}
<div className="relative">
{/* Circle with gradient background */}
<div className="circle-container">
<div className="circle-inner">
{/* Image placeholder with gradient */}
<div className="w-full h-full rounded-full bg-gradient-to-br from-gray-200 via-gray-300 to-gray-400 flex items-center justify-center overflow-hidden">
<img
src={item.img}
alt={item.subtitle}
className="w-full h-full object-cover"
onError={(e) => {
// Fallback if image doesn't load
e.target.style.display = 'none';
e.target.parentElement.innerHTML = `
<div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-banorte-blue to-banorte-red">
<span class="text-white text-2xl font-bold">${item.subtitle.charAt(0)}</span>
</div>
`;
}}
/>
</div>
</div>
</div>
</div>
{/* Text Below Circle */}
<div className="text-center mt-6 space-y-1">
<p className="text-banorte-blue font-bold tracking-wide item-title">
{item.title}
</p>
<p className="text-banorte-red font-bold tracking-wider item-subtitle">
{item.subtitle}
</p>
</div>
</div>
);
})}
{/* Right Arrow - positioned next to center item */}
<button
onClick={handleNext}
className="absolute left-1/2 translate-x-[168px] z-30 transition-all duration-200 hover:scale-110"
aria-label="Next"
>
<img
src="/images/flecha-der.png"
alt="Next"
className="w-12 h-12"
/>
</button>
</div>
</div>
</div>
</section>
);
};
export default Carousel;

36
src/components/Header.jsx Normal file
View File

@@ -0,0 +1,36 @@
import React from 'react';
const Header = () => {
return (
<header className="bg-banorte-red text-white shadow-lg border-b-4 border-banorte-red">
<div className="max-w-[1700px] mx-auto px-4 md:px-6 lg:px-8 py-1 flex items-center justify-between">
{/* Logo Section */}
<div className="flex items-center">
<img
src="/images/banortelogo.png"
alt="Banorte Logo"
className="h-10 w-auto"
/>
</div>
{/* User Section
<div className="group relative flex items-center space-x-3 bg-white/10 hover:bg-white/20 backdrop-blur-sm px-4 py-2 rounded-full cursor-pointer transition-all duration-300 hover:shadow-lg">
<div className="flex flex-col items-end">
<span className="text-xs text-white/70 font-light">Usuario</span>
<span className="text-sm md:text-base font-semibold text-white">A8072846</span>
</div>
<div className="relative">
<img
src="/images/chat_maya_default_avatar.png"
alt="Usuario"
className="w-10 h-10 md:w-12 md:h-12 rounded-full border-3 border-white shadow-lg group-hover:scale-110 transition-transform duration-300"
/>
<div className="absolute bottom-0 right-0 w-3 h-3 bg-green-500 rounded-full border-2 border-white"></div>
</div>
</div>*/}
</div>
</header>
);
};
export default Header;

17
src/components/Hero.jsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
const Hero = () => {
return (
<section className="relative w-full px-4 md:px-6 lg:px-8 pt-6 pb-3">
<div className="relative max-w-[1150px] mx-auto h-[320px] md:h-[360px] overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-b from-gray-900 via-red-950 to-gray-900">
<img
src="/images/hero_0.png"
alt="Maya Contigo"
className="w-full h-full object-fill rounded-3xl"
/>
</div>
</section>
);
};
export default Hero;

58
src/data/modules.js Normal file
View File

@@ -0,0 +1,58 @@
export const modules = [
{
id: 1,
title: "MAYA CONTIGO",
subtitle: "EGRESOS",
img: "/images/rh.png",
url: "https://playground.app.ia-innovacion.work/"
},
{
id: 2,
title: "MAYA CONTIGO",
subtitle: "NORMATIVA",
img: "/images/normativa.png",
url: "https://example.com/normativa"
},
{
id: 3,
title: "MAYA CONTIGO",
subtitle: "BURSATIL",
img: "/images/pruebas.png",
url: "https://example.com/cx"
},
{
id: 4,
title: "MAYA CONTIGO",
subtitle: "INVERSIONISTAS",
img: "/images/riesgos.png",
url: "https://example.com/bursatil"
},
{
id: 5,
title: "MAYA CONTIGO",
subtitle: "OCP",
img: "/images/normativa.png",
url: "https://example.com/pyme"
},
{
id: 6,
title: "MAYA CONTIGO",
subtitle: "PYME",
img: "/images/pruebas.png",
url: "https://example.com/pyme"
},
{
id: 7,
title: "MAYA CONTIGO",
subtitle: "RIESGOS",
img: "/images/rh.png",
url: "https://example.com/pyme"
},
{
id: 8,
title: "MAYA CONTIGO",
subtitle: "CX",
img: "/images/normativa.png",
url: "https://example.com/pyme"
}
];

27
src/index.css Normal file
View File

@@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply bg-white text-gray-900 antialiased;
}
}
@layer utilities {
.text-shadow-red {
text-shadow: 0 0 20px rgba(200, 16, 46, 0.5);
}
.shadow-red-glow {
box-shadow: 0 0 40px rgba(200, 16, 46, 0.3);
}
.shadow-red-strong {
box-shadow: 0 8px 32px rgba(200, 16, 46, 0.4);
}
}
@media (max-height: 750px){
body{zoom: 0.8;}
}

10
src/main.jsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

19
tailwind.config.js Normal file
View File

@@ -0,0 +1,19 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
'banorte-red': '#C8102E',
'banorte-blue': '#001A4D',
},
fontFamily: {
'sans': ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
}

14
vite.config.js Normal file
View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: true,
allowedHosts: [
'5173--main--sebastian--sebastian--sfkl73v7pva4g.pit-1.try.coder.app',
'.coder.app',
'localhost'
]
}
})