# Hotel DatePicker Responsive

Un selector de fechas moderno y flexible para reservas de hotel y vuelos con soporte para calendario de precios y diseño responsive.

**Sitio web:** [https://calendario-arg4qzs55-ramons-projects-e601971a.vercel.app/](https://calendario-arg4qzs55-ramons-projects-e601971a.vercel.app/)

## Características

- **Diseño Responsive**: Adaptado para dispositivos móviles y escritorio
- **Selección de Rangos**: Permite seleccionar rangos de fechas (check-in y check-out)
- **Modo Fecha Única**: Opción para seleccionar una sola fecha
- **Calendario de Precios**: Integración con APIs de precios para mostrar tarifas
- **Personalizable**: Múltiples opciones de configuración y estilos
- **Variables CSS Personalizables**: Personalización completa de colores y estilos mediante variables CSS
- **Shadow DOM**: Aislamiento de estilos para evitar conflictos con CSS externo
- **Internacionalización**: Soporte para múltiples idiomas
- **Standalone**: Incluye Luxon y CSS directamente en el bundle
- **Sin Dependencias Externas**: No requiere jQuery ni otras bibliotecas
- **Optimizado para Rendimiento**: Caché de tarifas y renderizado eficiente
- **Iconos SVG Inline**: No requiere bibliotecas de iconos externas
- **Selección del Mismo Día**: Opción para permitir seleccionar el mismo día como fecha de inicio y fin (ideal para vuelos)
- **Conteo Flexible**: Opción para mostrar "días" en lugar de "noches" en el tooltip

## Mejoras recientes

### Nuevas Funcionalidades

#### Personalización de Estilos CSS

El calendario ahora permite personalizar completamente los colores y estilos mediante variables CSS:

```javascript
const datepicker = new HotelDatePicker('container-id', {
    // Colores principales
    primaryColor: '#3498db',
    primaryLight: '#ebf5fb',
    primaryDark: '#2980b9',

    // Colores para precios
    priceBest: '#27ae60',
    priceGood: '#f2faf0',
    priceBestLow: '#bbe5b3',

    // Colores de texto
    textPrimary: '#2c3e50',
    textSecondary: '#7f8c8d',
    textLight: '#ffffff',

    // Colores de fondo
    bgPrimary: '#ffffff',
    bgSecondary: '#f5f5f5',
    bgDisabled: '#e9ecef',

    // Colores de borde
    borderColor: '#bdc3c7',
    borderFocus: '#95a5a6',

    // Tamaños
    daySize: '40px',
    fontSizeNormal: '14px',
    fontSizeSmall: '12px',
    fontSizeXsmall: '10px',

    // Espaciado
    spacingXs: '2px',
    spacingSm: '5px',
    spacingMd: '10px',
    spacingLg: '15px',
    spacingXl: '20px'
});
```

También puedes actualizar los estilos después de la inicialización:

```javascript
datepicker.updateCSSVars({
    primaryColor: '#e74c3c',
    primaryLight: '#fadbd8',
    priceBest: '#2ecc71'
});
```

#### Shadow DOM para Aislamiento de Estilos

El calendario ahora utiliza Shadow DOM para aislar sus estilos del resto de la página, evitando conflictos con CSS externo:

```html
<div id="calendar-container" style="position: relative;">
    <!-- El calendario se renderizará aquí dentro de un Shadow DOM -->
</div>
```

Es importante que el contenedor tenga `position: relative` para que el calendario se posicione correctamente.

#### Inyección de CSS Personalizado

Ahora puedes inyectar CSS personalizado directamente en el calendario de dos formas:

1. **Al inicializar el calendario**:

```javascript
const datepicker = new HotelDatePicker('container-id', {
    // Otras opciones...
    customCSS: `
        .day.selected {
            background-color: #3498db !important;
            border-radius: 50% !important;
        }

        .day.in-range {
            background-color: #e0f7fa !important;
        }
    `
});
```

2. **Después de la inicialización**:

```javascript
datepicker.injectCSS(`
    .day.selected {
        background-color: #3498db !important;
        border-radius: 50% !important;
    }

    .day.in-range {
        background-color: #e0f7fa !important;
    }
`);
```

#### Variables CSS en CSS Personalizado

También puedes definir variables CSS directamente en el CSS personalizado, y estas tendrán prioridad sobre las variables predeterminadas:

1. **Usando el selector `:host`**:

```javascript
datepicker.injectCSS(`
    :host {
        --hdp-primary-color: #3498db;
        --hdp-primary-light: #ebf5fb;
        --hdp-primary-dark: #2980b9;
    }

    .day.selected {
        background-color: var(--hdp-primary-color) !important;
        border-radius: 50% !important;
    }
`);
```

2. **Definiendo variables directamente sin selectores**:

```javascript
datepicker.injectCSS(`
    --hdp-primary-color: #3498db;
    --hdp-primary-light: #ebf5fb;
    --hdp-primary-dark: #2980b9;
`);
```

Estas funcionalidades te permiten personalizar completamente el aspecto del calendario sin tener que modificar el archivo CSS original.

#### Selección del Mismo Día

La opción `allowSameDaySelection` permite seleccionar el mismo día como fecha de inicio y fin, lo que es útil para vuelos o reservas de un solo día:

```javascript
const datepicker = new HotelDatePicker('container-id', {
    allowSameDaySelection: true
});
```

Cuando esta opción está habilitada:
- Se puede seleccionar el mismo día como fecha de inicio y fin
- El día se muestra como un círculo completo sin flechas de dirección
- El conteo de días comienza desde 1 (el mismo día cuenta como 1 día)

#### Mostrar Días en lugar de Noches

La opción `displayDaysInsteadOfNights` permite mostrar "días" en lugar de "noches" en el tooltip:

```javascript
const datepicker = new HotelDatePicker('container-id', {
    displayDaysInsteadOfNights: true
});
```

Cuando esta opción está habilitada:
- El tooltip muestra "días" en lugar de "noches"
- El conteo se ajusta para reflejar días en lugar de noches

### Estructura modular

El código ha sido reorganizado en módulos independientes para facilitar el mantenimiento y la extensión:

- `calendar.js`: Funciones para generar y manipular el calendario
- `config.js`: Configuración por defecto
- `dom.js`: Manipulación del DOM
- `events.js`: Manejo de eventos
- `initializer.js`: Inicialización del calendario
- `mobile.js`: Funcionalidades específicas para móvil
- `navigation.js`: Navegación entre meses
- `rates.js`: Gestión de tarifas
- `renderer.js`: Renderización del calendario
- `utils.js`: Funciones de utilidad

### Optimización de rendimiento

- Caché de tarifas para evitar cargas innecesarias
- Renderizado optimizado para evitar parpadeos
- Detección inteligente de meses visibles en móvil
- Aplicación eficiente de estilos

### Mejoras en la vista móvil

- Mejor respuesta táctil
- Scroll más suave
- Mejor visualización de los botones
- Optimización del espacio en pantalla
- Carga de tarifas solo para meses visibles

## Instalación

```bash
npm install hotel-datepicker-responsive --save
```

## Uso Básico

```html
<div id="datepicker"></div>

<script>
  const datepicker = new HotelDatePicker('datepicker', {
    minNights: 1,
    maxNights: 30,
    format: 'dd/MM/yyyy',
    lang: 'es'
  });
</script>
```

### Versión Standalone (Recomendada)

Esta versión incluye Luxon y CSS directamente en el bundle, por lo que solo necesitas incluir un archivo:

```html
<!-- Incluir Hotel DatePicker (con Luxon y CSS incluidos) -->
<script src="dist/hotel-datepicker.standalone.js"></script>
```

Para generar estos archivos, ejecuta:
```
npm run build
```

Esto creará en la carpeta `dist`:
- `hotel-datepicker.standalone.js`: Librería completa con Luxon y CSS incluidos

### Inicialización del componente

Una vez incluidos los archivos necesarios, inicializa el componente:

```javascript
const datePicker = new HotelDatePicker('container-id', {
    minNights: 1,
    maxNights: 30,
    onDateSelect: function(dates) {
        console.log('Fechas seleccionadas:', dates);
    }
});
```

## Servidor Python incluido

Para facilitar la visualización del calendario, se incluye un servidor Python simple:

1. Ejecuta `start_server.bat` o `python server.py` desde la línea de comandos
2. Abre tu navegador en http://localhost:8000/HotelDatePicker.html

## Ejemplos incluidos

Puedes ver ejemplos en vivo en: [https://calendario-arg4qzs55-ramons-projects-e601971a.vercel.app/examples/basic.html](https://calendario-arg4qzs55-ramons-projects-e601971a.vercel.app/examples/basic.html)

El proyecto incluye varios ejemplos para mostrar diferentes funcionalidades:

### Ejemplo básico

Muestra el uso básico del calendario con opciones para ocultar leyenda y atajos.

```
examples/basic.html
```

### Ejemplo con tarifas

Muestra cómo integrar el calendario con una API de tarifas.

```
examples/with-rates.html
```

### Ejemplo móvil

Muestra el calendario optimizado para dispositivos móviles.

```
examples/mobile.html
```

### Ejemplo para vuelos (selección del mismo día)

Muestra cómo configurar el calendario para reservas de vuelos, permitiendo seleccionar el mismo día y mostrando días en lugar de noches.

```javascript
const datepicker = new HotelDatePicker('container-id', {
    isRange: true,
    allowSameDaySelection: true,
    displayDaysInsteadOfNights: true,
    i18n: {
        day: 'día',
        days: 'días'
    }
});
```

### Ejemplo de modo fecha única

Muestra cómo configurar el calendario para seleccionar una sola fecha.

```javascript
const datepicker = new HotelDatePicker('container-id', {
    isRange: false
});
```

## Opciones de configuración

| Opción | Tipo | Valor por defecto | Descripción |
|--------|------|-------------------|-------------|
| minNights | Number | 1 | Número mínimo de noches |
| maxNights | Number | 30 | Número máximo de noches |
| initialStartDate | String | null | Fecha de inicio inicial (formato ISO) |
| initialEndDate | String | null | Fecha de fin inicial (formato ISO) |
| format | String | 'dd LLL yyyy' | Formato de fecha (usando Luxon) |
| hideLegend | Boolean | false | Oculta la leyenda |
| hideShortcuts | Boolean | false | Oculta los atajos rápidos |
| mobileBreakpoint | Number | 768 | Punto de quiebre para vista móvil (en píxeles) |
| onDateSelect | Function | () => {} | Callback cuando se seleccionan fechas |
| onMonthChange | Function | () => {} | Callback cuando cambia el mes visible |
| onView | Function | () => {} | Callback cuando se abre el calendario |
| priceCalendar | Object | { enabled: false } | Configuración del calendario de precios |
| i18n | Object | { ... } | Textos para internacionalización |
| isRange | Boolean | true | Si es true, permite seleccionar un rango de fechas. Si es false, solo permite seleccionar una fecha |
| allowSameDaySelection | Boolean | false | Si es true, permite seleccionar el mismo día como fecha de inicio y fin (útil para vuelos) |
| displayDaysInsteadOfNights | Boolean | false | Si es true, muestra "días" en lugar de "noches" en el tooltip |
| currency | String | 'MXN' | Moneda para mostrar en la leyenda de precios |
| primaryColor | String | '#db006e' | Color principal del calendario |
| primaryLight | String | '#fcf5f9' | Color principal claro (para días en rango) |
| primaryDark | String | '#800d46' | Color principal oscuro |
| priceBest | String | '#1c8207' | Color para los mejores precios |
| priceGood | String | '#f2faf0' | Color para buenos precios |
| priceBestLow | String | '#bbe5b3' | Color para mejores precios bajos |
| textPrimary | String | '#333333' | Color de texto principal |
| textSecondary | String | '#696770' | Color de texto secundario |
| textLight | String | '#ffffff' | Color de texto claro |
| bgPrimary | String | '#ffffff' | Color de fondo principal |
| bgSecondary | String | '#f5f5f5' | Color de fondo secundario |
| bgDisabled | String | '#e9ecef' | Color de fondo para elementos deshabilitados |
| borderColor | String | '#e6e6e6' | Color de borde |
| borderFocus | String | '#adb5bd' | Color de borde para elementos enfocados |
| daySize | String | '40px' | Tamaño de los días en el calendario |
| fontSizeNormal | String | '14px' | Tamaño de fuente normal |
| fontSizeSmall | String | '12px' | Tamaño de fuente pequeña |
| fontSizeXsmall | String | '10px' | Tamaño de fuente muy pequeña |
| spacingXs | String | '2px' | Espaciado extra pequeño |
| spacingSm | String | '5px' | Espaciado pequeño |
| spacingMd | String | '10px' | Espaciado mediano |
| spacingLg | String | '15px' | Espaciado grande |
| spacingXl | String | '20px' | Espaciado extra grande |
| customCSS | String | '' | CSS personalizado para inyectar directamente en el Shadow DOM |


## Métodos disponibles

### open()

Abre el calendario.

```javascript
datePicker.open();
```

### close()

Cierra el calendario.

```javascript
datePicker.close();
```

### toggle()

Alterna entre abrir y cerrar el calendario.

```javascript
datePicker.toggle();
```

### setRates(year, month, rates)

Establece las tarifas para un mes específico.

```javascript
// Formato simple de tarifas
const rates = {
    '2023-05-01': 1200,
    '2023-05-02': 1500,
    '2023-05-03': 1800,
    '2023-05-04': 1000,
    '2023-05-05': 900
};

datePicker.setRates(2023, 5, rates);
```

### getSelectedDates()

Obtiene las fechas seleccionadas.

```javascript
const dates = datePicker.getSelectedDates();
console.log(dates.startDate, dates.endDate, dates.nights);
```

### setSelectedDates(startDate, endDate)

Establece las fechas seleccionadas.

```javascript
datePicker.setSelectedDates('2023-05-01', '2023-05-05');
```

### clearSelectedDates()

Limpia las fechas seleccionadas.

```javascript
datePicker.clearSelectedDates();
```

### clearRatesCache()

Limpia la caché de tarifas.

```javascript
datePicker.clearRatesCache();
```

### updateCSSVars(cssVars)

Actualiza las variables CSS personalizadas.

```javascript
datePicker.updateCSSVars({
    primaryColor: '#3498db',
    primaryLight: '#ebf5fb',
    priceBest: '#27ae60'
});
```

### injectCSS(css)

Inyecta CSS personalizado en el Shadow DOM.

```javascript
datePicker.injectCSS(`
    .day.selected {
        background-color: #3498db !important;
        border-radius: 50% !important;
    }

    .day.in-range {
        background-color: #e0f7fa !important;
    }
`);
```

## Estructura del proyecto

```
hotel-date-picker/
├── dist/                  # Archivos compilados
│   ├── hotel-date-picker.js
│   ├── hotel-date-picker.min.js
│   └── luxon.min.js
├── examples/              # Ejemplos de uso
│   ├── basic.html
│   ├── with-rates.html
│   └── mobile.html
├── src/                   # Código fuente
│   ├── modules/           # Módulos
│   │   ├── calendar.js
│   │   ├── config.js
│   │   ├── dom.js
│   │   ├── events.js
│   │   ├── index.js
│   │   ├── initializer.js
│   │   ├── mobile.js
│   │   ├── navigation.js
│   │   ├── rates.js
│   │   ├── renderer.js
│   │   └── utils.js
│   ├── HotelDatePicker.js
│   ├── HotelDatePicker.modular.js
│   ├── HotelDatePicker.css
│   ├── HotelDatePicker.combined.css
│   └── styles.css
├── package.json
└── README.md
```

## Publicación en npm

Para publicar la librería en npm:

1. Actualiza la información en `package.json` (nombre, autor, repositorio, etc.)
2. Ejecuta `npm run build` para generar los archivos
3. Ejecuta `npm login` para iniciar sesión en npm
4. Ejecuta `npm publish` para publicar el paquete

Los usuarios podrán instalar tu paquete con:
```
npm install hotel-datepicker-responsive
```

Y usarlo en sus proyectos:

```html
<!-- Incluir Hotel DatePicker (con Luxon y CSS incluidos) -->
<script src="node_modules/hotel-datepicker-responsive/dist/hotel-datepicker.standalone.js"></script>

<script>
  const datepicker = new HotelDatePicker('container-id', {
    // opciones...
  });
</script>
```

## Internacionalización

El calendario soporta múltiples idiomas para los nombres de meses, días y textos:

```javascript
const datepicker = new HotelDatePicker('container-id', {
    lang: 'es', // Código de idioma (es, en, fr, etc.)
    i18n: {
        night: 'noche',
        nights: 'noches',
        day: 'día',         // Nuevo: para displayDaysInsteadOfNights
        days: 'días',       // Nuevo: para displayDaysInsteadOfNights
        apply: 'Aplicar',
        cancel: 'Cancelar',
        selectDate: 'Seleccionar fecha',
        selectCheckout: 'Seleccionar salida',
        selectCheckin: 'Seleccionar entrada'
    }
});
```

## Repositorio

El código fuente está disponible en GitHub:
[https://github.com/JoseRamonGraciaCalderon/HotelDatePicker](https://github.com/JoseRamonGraciaCalderon/HotelDatePicker)

## Licencia

MIT
