Un homenaje a la fiesta de València en el día de Sant Josep
Hoy, 19 de marzo, València celebra Sant Josep, el día grande de las Fallas, una fiesta declarada Patrimonio Inmaterial de la Humanidad por la UNESCO. Durante estas fechas, la ciudad se transforma en un espectáculo vibrante de arte, luz, música y pólvora, donde las tradiciones centenarias se fusionan con la creatividad más innovadora. Las Fallas, monumentos efímeros que culminan con la emblemática Cremà (la quema de los monumentos), simbolizan el renacimiento y la renovación, cerrando un ciclo lleno de emociones.
En este contexto, hemos creado un mapa interactivo de las Fallas de València, una herramienta digital que permitirá a visitantes y locales explorar las ubicaciones de las fallas y descubrir detalles fascinantes sobre sus artistas y comisiones. Este mapa interactivo no solo es una guía práctica, sino también una ventana a la riqueza cultural de esta celebración. Para lograrlo, combinamos las potentes herramientas de VueJS 3 para la lógica de la aplicación, Leaflet para la visualización geográfica y Tailwind CSS para un diseño visual atractivo y coherente.
Este proyecto no solo busca celebrar la riqueza cultural de las Fallas, sino también demostrar cómo la tecnología puede enriquecer experiencias culturales y turísticas. A través de este mapa interactivo de las Fallas de València, los usuarios podrán planificar su recorrido por la ciudad de manera más eficiente, asegurándose de no perderse ninguna de las impresionantes creaciones que llenan las calles durante esta festividad.

En las siguientes secciones, te guiaré paso a paso por el proceso de creación de este mapa interactivo, desde la configuración inicial hasta la implementación de funcionalidades avanzadas. Este proyecto es una excelente oportunidad para explorar cómo la tecnología puede fusionarse con la cultura para crear experiencias únicas y memorables.
Las Fallas de València son una de las festividades más emblemáticas de España, celebradas anualmente en honor a San José. Cada año, más de 350 comisiones falleras construyen monumentos llamados fallas, diseñados por artistas locales y dedicados a la sátira social. Estos monumentos efímeros son testigos de una tradición centenaria que combina arte, tradición y cultura.
Tipos de Fallas
Existen dos tipos principales de fallas:
- Fallas Mayores: Son los monumentos principales, de gran tamaño y elaborados con materiales como cartón, madera y poliestireno. Se sitúan en plazas y calles principales de la ciudad, destacándose por su complejidad y creatividad.
- Fallas Infantiles: Versiones más pequeñas y adaptadas para niños, generalmente con temáticas más amigables y educativas. Se plantan junto a las fallas mayores y también participan en el concurso oficial.


Las fallas se dividen en distintas secciones según su tamaño y elaboración:
- Sección Especial: Las fallas más grandes y espectaculares.
- Primera A, Primera B, Segunda, Tercera, etc.: Clasificadas según su complejidad y presupuesto.
La Cremà
Cada 19 de marzo, todas las fallas son quemadas en la Cremà, un evento que simboliza el ciclo de la vida y el renacimiento. Esta tradición se remonta a la antigua costumbre de los carpinteros valencianos, quienes quemaban estructuras de madera llamadas «parots» para celebrar la llegada de la primavera.

La Mascletà: El Ritmo de las Fallas
No se puede hablar de las Fallas de València sin mencionar la Mascletà, un espectáculo pirotécnico único que llena de sonido y emoción la Plaza del Ayuntamiento cada día del 1 al 19 de marzo. A las 14:00 horas en punto, la ciudad se detiene para vibrar al ritmo de los estruendos controlados de los petardos, que crean una sinfonía de explosiones perfectamente sincronizadas.
La Mascletà no es solo un espectáculo visual y auditivo; es una experiencia sensorial que define la esencia de las Fallas. Para los valencianos, es un momento de orgullo y tradición, mientras que para los visitantes, es una oportunidad única de sumergirse en la cultura local. Si estás planeando tu visita a València durante las Fallas, incluir la Mascletà en tu itinerario es imprescindible. Y con nuestro mapa interactivo, podrás ubicar fácilmente la Plaza del Ayuntamiento y planificar tu día para no perderte este increíble evento.

La Ofrenda de Flores: Un Tributo Lleno de Color y Emoción
Uno de los momentos más conmovedores de las Fallas es la Ofrenda de Flores a la Virgen de los Desamparados, patrona de València. Durante los días 17 y 18 de marzo, miles de falleros y falleras visten sus trajes tradicionales y desfilan hacia la Plaza de la Virgen, donde depositan ramos de flores para crear un impresionante tapiz floral. Este mosaico, que puede alcanzar varios metros de altura, representa a la Virgen y se convierte en un símbolo de devoción y comunidad.
La Ofrenda no es solo un acto religioso; es una celebración de la identidad valenciana y un recordatorio del poder de la unión y la tradición. Con nuestro mapa interactivo, podrás ubicar la Plaza de la Virgen y planificar tu visita para no perderte este espectáculo lleno de color, música y emoción.


Historia de las Fallas
El origen de las Fallas se remonta a la tradición medieval de los carpinteros valencianos. Con el tiempo, estas hogueras evolucionaron en construcciones artísticas con figuras humanas, dando lugar a los monumentos falleros que conocemos hoy.
En el siglo XIX, las Fallas comenzaron a adoptar una fuerte carga satírica, representando críticas sociales y políticas a través de sus figuras, conocidas como «ninots». Con el paso de los años, el arte fallero se ha profesionalizado, dando lugar a una competencia entre comisiones falleras por crear el monumento más impresionante y mejor valorado.
Días claves de la Fiesta
- La Plantà (15 de marzo): Momento en el que las fallas son levantadas y presentadas al público.
- La Mascletà (1-19 de marzo): Espectáculo pirotécnico diario en la Plaza del Ayuntamiento.
- La Ofrenda (17-18 de marzo): Acto en el que los falleros desfilan hasta la Plaza de la Virgen para ofrecer flores a la Virgen de los Desamparados.
- La Nit del Foc (18 de marzo): Gran castillo de fuegos artificiales que ilumina la ciudad.
- La Cremà (19 de marzo): Quema de todas las fallas, cerrando la festividad.
Impacto Social y Cultural
Las Fallas no solo son una celebración, sino también un fenómeno cultural y social que enriquece la vida de la ciudad de Valencia y sus habitantes. Fomentan un sentido de identidad y orgullo entre los residentes locales, fortaleciendo los lazos sociales y promoviendo un sentido de pertenencia a la comunidad. Además, las Fallas propician la creatividad colectiva y la salvaguardia de las artes y artesanías tradicionales, contribuyendo a la cohesión social y al bienestar de la ciudad.
Tabla de contenidos
Tecnologías Utilizadas
Para desarrollar este mapa interactivo de las Fallas de València, se han seleccionado las siguientes herramientas tecnológicas:
1. VueJS 3
VueJS es un framework progresivo de JavaScript que facilita la creación de interfaces reactivas y modulares. En este proyecto, se utiliza Vue 3 con la Composition API y la sintaxis script setup
, lo que optimiza la organización del código y mejora la eficiencia en la gestión del estado.
Ventajas de VueJS 3:
- Reactividad eficiente: Se gestiona el estado de la aplicación de manera efectiva, asegurando que los cambios sean reflejados en tiempo real. Esto permite una experiencia de usuario más fluida y dinámica.
- Componentización: El código es organizado en unidades reutilizables, lo que facilita la mantenibilidad y escalabilidad del proyecto. Los componentes pueden ser reutilizados en diferentes partes de la aplicación, reduciendo la duplicación de código.
- Eventos personalizados: Se mejoran la comunicación entre componentes, permitiendo una integración más fluida de funcionalidades avanzadas. Esto permite crear interfaces complejas con interacciones intuitivas.
- Soporte para TypeScript: Se ofrece soporte completo para TypeScript, lo que ayuda a detectar errores en tiempo de compilación y mejora la calidad del código. Esto reduce el riesgo de errores en producción y facilita el mantenimiento a largo plazo.
- Optimización del rendimiento: Se optimiza el rendimiento de la aplicación mediante técnicas como el lazy loading y el uso eficiente de recursos, lo que garantiza una experiencia rápida y eficiente para los usuarios.
2. Leaflet
Leaflet es una librería ligera y potente para crear mapas interactivos. Se permite visualizar datos geoespaciales de forma intuitiva y personalizar la experiencia del usuario.
Funciones clave en Leaflet:
- Capas base: Se utilizan fuentes como OpenStreetMap y ortofotos satelitales para proporcionar una base sólida para el mapa. Esto permite una visualización precisa y detallada de las ubicaciones geográficas.
- Marcadores dinámicos: Cada falla es representada por un icono según su sección, lo que facilita la identificación visual. Los marcadores pueden ser personalizados para reflejar diferentes categorías o características.
- Clustering: Los marcadores son agrupados automáticamente en zonas con alta densidad, mejorando la experiencia del usuario al visualizar áreas con muchos puntos de interés. Esto reduce la sobrecarga visual y facilita la exploración de zonas congestionadas.
- Interacción con el mapa: Se permite a los usuarios interactuar con el mapa mediante gestos como zoom, arrastre y clics, lo que proporciona una experiencia de usuario más inmersiva y exploratoria.
3. Tailwind CSS
Tailwind CSS es un framework de utilidades que permite diseñar interfaces sin escribir CSS adicional. Se facilita el desarrollo rápido y consistente mediante su sistema de clases.
Ventajas de Tailwind CSS:
- Diseño responsivo sin esfuerzo: Los estilos son adaptados automáticamente a diferentes dispositivos y tamaños de pantalla, lo que garantiza una experiencia óptima en cualquier dispositivo.
- Estilos predefinidos: Se proporciona una apariencia uniforme y coherente a lo largo de toda la aplicación, lo que mejora la consistencia visual y reduce el tiempo de diseño.
- Fácil personalización: Se permite ajustar colores, espaciado y tipografía mediante un archivo de configuración, lo que facilita la creación de un diseño personalizado sin necesidad de escribir CSS personalizado. Esto permite a los desarrolladores centrarse en la funcionalidad principal del proyecto.
- Soporte para animaciones y transiciones: Se facilita la implementación de animaciones y transiciones suaves, lo que enriquece la experiencia del usuario y mejora la interacción con la aplicación.
Estas tecnologías combinadas permiten crear un mapa interactivo que no solo es visualmente atractivo, sino también altamente funcional y fácil de usar. La integración de VueJS, Leaflet y Tailwind CSS proporciona una base sólida para desarrollar aplicaciones web modernas y eficientes.
¿Quieres aprender LeafLet? Aquí te dejo un enlace tutorial básico de creación de mapas con LeafLet y una ampliación del tutorial básico, con mayor información y funcionalidades.
Estructura del Proyecto
La estructura del proyecto está organizada de forma modular y clara para facilitar el desarrollo y mantenimiento.
mapa-fallas/
├── public/ # Archivos estáticos
│ └── index.html # Plantilla HTML base
├── src/ # Código fuente de la aplicación
│ ├── assets/ # Recursos globales
│ │ └── tailwind.css # Estilos globales (Tailwind CSS)
│ ├── components/ # Componentes Vue reutilizables
│ │ ├── MapView.vue # Mapa interactivo con las fallas
│ │ ├── FallaInfo.vue # Panel de información de una falla
│ ├── App.vue # Componente raíz de la aplicación
│ └── main.js # Punto de entrada principal
├── node_modules/ # Dependencias instaladas (autogenerado)
├── package.json # Dependencias y scripts del proyecto
├── vite.config.js # Configuración de Vite
├── tailwind.config.js # Configuración de Tailwind CSS
└── postcss.config.js # Configuración de PostCSS (autogenerado)
A continuación, te explico cada parte brevemente:
Directorio Raíz (mapa-fallas/
)
Contiene los ficheros principales del proyecto:
package.json
: Define dependencias y scripts (comonpm run dev
).vite.config.js
: Configura Vite para VueJS.tailwind.config.js
: Personaliza Tailwind CSS.
Carpeta public/
Archivos estáticos que no necesitan procesamiento:
index.html
: Plantilla base donde se monta la aplicación.- Puedes añadir imágenes, fuentes u otros recursos aquí.
Directorio src/
Contiene todo el código fuente de la aplicación.
assets/
- Archivos globales como estilos (
tailwind.css
) o imágenes.
components/
MapView.vue
: Muestra el mapa interactivo con las fallas.FallaInfo.vue
: Muestra detalles de una falla seleccionada.
App.vue
- Componente raíz que coordina el mapa y el panel de información.
main.js
Punto de entrada principal que inicializa VueJS.
Construcción del proyecto
Para construir y ejecutar el proyecto, es fundamental seguir un conjunto de pasos estructurados que garantizan la correcta configuración del entorno de desarrollo. Estos pasos incluyen la creación del proyecto base, la instalación de las dependencias necesarias y la configuración de herramientas clave como Vite , VueJS , Leaflet y Tailwind CSS .
Lo primero e indispensable es tener instalado Node.js y npm en tu sistema. Node.js es un entorno de ejecución de JavaScript que permite trabajar con frameworks modernos, mientras que npm (Node Package Manager) es una herramienta para gestionar paquetes y dependencias. Puedes verificar si los tienes instalados ejecutando los siguientes comandos en tu terminal:
node -v
npm -v
Si no están instalados, puedes descargarlos desde el sitio oficial de Node.js , ya que npm se instala automáticamente junto con Node.js.
Una vez que tengas Node.js y npm configurados, puedes proceder con los comandos necesarios para instalar y generar el proyecto desde cero. Estos comandos están diseñados para ser ejecutados en tu terminal o línea de comandos, y cada uno tiene un propósito específico, desde la inicialización del proyecto hasta la ejecución del servidor de desarrollo, asegurando que todo esté listo para comenzar a trabajar en la aplicación.
Con esta introducción, estarás preparado para seguir los pasos necesarios y tener un entorno de desarrollo funcional en pocos minutos. ¡Comencemos! 🚀
1. Crear el Proyecto con Vite
npm create vite@latest mapa-fallas --template vue
- Propósito: Inicializa un nuevo proyecto utilizando Vite , una herramienta rápida para construir aplicaciones modernas.
- Detalles:
npm create vite@latest
: Ejecuta el generador de proyectos de Vite.mapa-fallas
: Especifica el nombre del directorio donde se creará el proyecto.--template vue
: Indica que queremos usar VueJS como framework base.
- Resultado: Se genera una estructura básica de archivos y carpetas con configuraciones predeterminadas para un proyecto VueJS.
2. Entrar al Directorio del Proyecto
cd mapa-fallas
- Propósito: Navegar al directorio del proyecto recién creado.
- Detalles: Este comando es necesario para ejecutar comandos dentro del contexto del proyecto (como instalar dependencias o iniciar el servidor).
- Resultado: Ahora estás dentro del directorio
mapa-fallas
, listo para continuar con la configuración.
3. Instalar Dependencias Básicas
npm install
- Propósito: Instala las dependencias básicas definidas en el archivo
package.json
. - Detalles:
- Este comando lee el archivo
package.json
y descarga todas las bibliotecas necesarias para que el proyecto funcione. - Las dependencias incluyen VueJS, Vite y otras herramientas preconfiguradas.
- Este comando lee el archivo
- Resultado: Se crea la carpeta
node_modules/
, que contiene todas las bibliotecas instaladas.
4. Instalar Leaflet y ESRI Leaflet
npm install leaflet esri-leaflet
- Propósito: Añade soporte para mapas interactivos y capas geoespaciales.
- Detalles:
leaflet
: Biblioteca ligera para crear mapas interactivos.esri-leaflet
: Plugin que permite cargar datos geoespaciales desde ArcGIS.
- Resultado: Ahora puedes usar Leaflet y ESRI Leaflet para mostrar las ubicaciones de las fallas en el mapa.
5. Instalar Tailwind CSS
npm install tailwindcss postcss autoprefixer --save-dev
- Propósito: Añade soporte para estilizar la aplicación con Tailwind CSS .
- Detalles:
tailwindcss
: Framework de utilidades CSS para diseño rápido y responsivo.postcss
: Herramienta para procesar CSS.autoprefixer
: Añade prefijos automáticos para compatibilidad con navegadores.--save-dev
: Indica que estas dependencias son solo para desarrollo.
- Resultado: Prepara el proyecto para usar Tailwind CSS en tus estilos.
6. Inicializar Tailwind CSS
npx tailwindcss init -p
- Propósito: Genera archivos de configuración para Tailwind CSS y PostCSS.
- Detalles:
- Crea dos archivos:
tailwind.config.js
: Configuración personalizable de Tailwind.postcss.config.js
: Configuración para procesar CSS con PostCSS.
-p
: Genera automáticamente el archivo de configuración de PostCSS.
- Crea dos archivos:
- Resultado: Ahora puedes configurar Tailwind CSS según las necesidades del proyecto.
7. Iniciar el Servidor de Desarrollo
npm run dev
- Propósito: Inicia el servidor de desarrollo para trabajar en tiempo real.
- Detalles:
- Este comando compila el código fuente y sirve la aplicación en un entorno local.
- La aplicación generalmente se abre en
http://localhost:5173
.
- Resultado: Puedes ver y probar tu aplicación en el navegador mientras desarrollas.
8. Compilar para Producción
npm run build
- Propósito: Compila el proyecto para producción.
- Detalles:
- Optimiza los archivos para mejorar el rendimiento (minificación, eliminación de código no utilizado, etc.).
- El resultado se guarda en la carpeta
dist/
.
- Resultado: Genera una versión optimizada de la aplicación lista para implementar.
Componentes del proyecto
El corazón de esta aplicación interactiva son sus componentes , que están diseñados para ser modulares, reutilizables y fáciles de mantener. Cada componente tiene una responsabilidad específica dentro del proyecto, lo que permite una separación clara de las funcionalidades y un flujo de trabajo estructurado.
En este apartado, exploraremos en detalle los tres componentes principales que conforman la aplicación:
App.vue
: El componente raíz que actúa como el punto central de coordinación entre el mapa y el panel de información.MapView.vue
: El componente encargado de mostrar el mapa interactivo con las ubicaciones de las fallas y gestionar la interacción del usuario.FallaInfo.vue
: El componente que muestra información detallada sobre una falla seleccionada, proporcionando una experiencia dinámica al usuario.
Cada uno de estos componentes está construido utilizando tecnologías modernas como VueJS 3 , Leaflet y Tailwind CSS , lo que garantiza un diseño limpio, responsivo y altamente funcional. Además, se integran entre sí a través de eventos y propiedades, asegurando una comunicación fluida y una experiencia de usuario cohesionada.
A continuación, desglosaremos cada componente, explicando su propósito, estructura y cómo interactúa con el resto de la aplicación.
Componente App.vue
: El Núcleo de la Aplicación
El componente App.vue
es el corazón de nuestra aplicación. Actúa como el punto central donde se coordina la interacción entre los demás componentes (MapView
y FallaInfo
) y se gestiona el estado global de la aplicación. En este componente, definimos la lógica principal que permite al usuario interactuar con el mapa y ver información detallada sobre las fallas seleccionadas.
<template>
<div class="flex h-screen w-full">
<!-- Contenedor del mapa -->
<div :class="['h-full', selectedFalla ? 'w-3/4' : 'w-full']">
<MapView @falla-seleccionada="selectedFalla = $event" />
</div>
<!-- Panel de información -->
<FallaInfo
v-if="selectedFalla"
:falla="selectedFalla"
@close="closeFallaInfo"
/>
</div>
</template>
¿Qué vamos a hacer con App.vue
?
- Gestión del Estado Global:
- Vamos a usar VueJS para crear una variable reactiva (
selectedFalla
) que almacene la falla seleccionada en el mapa. - Esta variable determinará si se muestra el panel de información (
FallaInfo
) o no.
- Vamos a usar VueJS para crear una variable reactiva (
- Coordinación entre Componentes:
- Escucharemos eventos emitidos por el componente
MapView
cuando el usuario seleccione una falla en el mapa. - Pasaremos los datos de la falla seleccionada al componente
FallaInfo
mediante props. - También escucharemos un evento emitido por
FallaInfo
para cerrar el panel de información.
- Escucharemos eventos emitidos por el componente
- Diseño Dinámico:
- Usaremos clases condicionales de Tailwind CSS para ajustar dinámicamente el diseño:
- Si no hay una falla seleccionada, el mapa ocupará todo el ancho de la pantalla (
w-full
). - Si hay una falla seleccionada, el mapa reducirá su ancho (
w-3/4
) para dar espacio al panel de información (w-1/4
).
- Si no hay una falla seleccionada, el mapa ocupará todo el ancho de la pantalla (
- Usaremos clases condicionales de Tailwind CSS para ajustar dinámicamente el diseño:
- Estructura Modular:
- Importaremos los componentes hijos (
MapView
yFallaInfo
) y los integraremos dentro de la plantilla. - Esto garantiza que cada componente tenga una responsabilidad clara y sea fácil de mantener.
- Importaremos los componentes hijos (
Componente MapView.vue
: El Mapa Interactivo
El componente MapView.vue
es el encargado de mostrar el mapa interactivo con las ubicaciones de las fallas y gestionar la interacción del usuario. Este es uno de los componentes más complejos del proyecto, ya que combina tecnologías como Leaflet , ESRI Leaflet y VueJS para cargar datos geoespaciales, renderizar marcadores personalizados y manejar eventos.
<template>
<div class="h-screen w-full flex">
<div id="map" class="h-full flex-grow"></div>
</div>
</template>
<script setup>
import {ref, onMounted, defineEmits} from "vue";
import L from "leaflet";
import "leaflet-fullscreen";
import "leaflet/dist/leaflet.css";
import "leaflet-fullscreen/dist/leaflet.fullscreen.css";
import * as esriLeaflet from "esri-leaflet";
const map = ref(null);
const emit = defineEmits(["falla-seleccionada"]);
const specialSectionIconBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEzklEQVRYhe2Zv2/aaBjHPyDSClVpe1Y9mEpdysQYGlaGDolUtxNDcnPLCaEM+QPiEPIHdIgiq2TqcmRgOF1SiQ4dvDqlww2evJ0wgyVfqyqKaCy4wTZHqAFD6FWn63eC930NH55f7/s+JPiPKPG9AaLqB+iidW3QNjwFsjGQ/aHs0HSnD60YWEAtBa15v2cuUAuyfXjqw2XjgshSOkNcEIkL4mBdz7GlS9OQXdMAKFrecM2FvQce/LcDtaAIvIoByfUCN1bz3Mzlpz7X1TXO6yquaRQTUGzD3n2oLBzUt2IFkJPrBZJrBRLpTNTHuZnzfpBrGpzXVbq6tmuB7MKzKNaNBOrH4auldEa6tVmKZMGxX5jOcGfnANc0+Li/lU04dtuCR9Pidypo24vF3xPpDLfLykxWnAYsvDzG2d6g59jvp8FOBLW8DH6VSGe4u3NwJVEWobggcu/1O5ztDVzTmAg7EbQPlaV0RrpdVhYOOay7OweBZU+AVNiasaB+dsu3NksLc/c4xQWR5bLCp/0tqQ2VsGoQChq4PLlemJo4HcemWldRy8q1YIOq0NW1XaKC+sWc5Fph7AcfNRse6F82p7rGykPvvZzLI80ZJstlha7+GAuKKahNBY35tXKcy4+aDUqH1StjwfvSIbgnf8wFGhdEkusFLpqNCtNAg737xur8tfI6Sq4VuGg2pDY8vQ8nwXiYRbNxQZypqEuCSMexQ+c6jj1TKARejIF0ZXx0YQzkpRmyXM7lebKap1pXB7ClwypqWaFaV/lgGqhlZWZY1zSKDLk/NEZnqZkr6Qwv1gscvW0MQDuOzVGzgfSTyPM1b+7FWiEy7JIHeqWejnV9VJ3q2qACBJIEkWw6gySItEyDU10DmAmWaa6PIjmXp1r34vKDd9YcSPm5hLJZGlQGtayQmhDDYQoz1FygkiDy5+t3VOsq1V9VwAsBZbOEnMtTOqwOrPzmTOO5X4/nra/jQDs9x5ZCxr+Sslli5WGG0mEV/eWx97AfnwHYqa5562ZI0J5n/c5E0D60Lk1DHh0fp5QgXrFUEI8B6Iv1AtW6ym87B5FBL02D/sgpKqw8We5I3M0iz/Xe65Qg8sE0ZnJ5z7FxTYPYyKk/zPU1oNjVtblO8kdvG4MNILCunMtzqmvIEe9WQxzjQVPQsoDzujoTaLDXj5Yq+Ccc3pxpPFnNTwT+cqYBdEYP0OOyvuaaRtE1jaln0aBmrjz01k077mUnfJ5rGoFFT0bnQkFd2EtA8byucmdKEkiCeO2zaKDzugpettdG50JBH4BlwaOeY793tjcWfqUe6prQ91o8XyVRJFAYlKtHrmm8d7Y3WC4r1+qUBOrqGp8PqwNLTgL9CjYMa8HjnmOfONsb3MzlWZ7zrj9sRSASZGTQABZI1aHS1bXdrv6YqM2ynmPT1TW+nA0yu4OX3aGJcy3QQH5zoGJB8aLZqFw0GxJ414fgChNYuufYXJoGQ2eHDp71Zm7qzt1x9u/dNb+JJvk7WYqrJ/Pv23Ee1lB8fbWbLFL/nz8b/i39DZC3NpRnOvPuAAAAAElFTkSuQmCC";
let fallasPorSector = [];
const capasPorSector = {};
const getColorForSection = (seccion) => {
if (!seccion) return "#000000";
const sectionNumber = parseInt(seccion.replace(/\D/g, ""));
if (isNaN(sectionNumber)) return "#000000";
const red = Math.min(255, 255 - (sectionNumber - 1) * 30);
const green = Math.min(255, (sectionNumber - 1) * 30);
return `rgb(${red}, ${green}, 0)`;
};
const generateNumberIcon = (seccion, isLarge = false) => {
const size = isLarge ? 48 : 24;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
ctx.fillStyle = getColorForSection(seccion);
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#FFFFFF";
ctx.font = `bold ${size / 2}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(seccion, size / 2, size / 2);
return canvas.toDataURL();
};
const normalizeSectorName = (text) => {
return text
? text
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/\s+/g, " ")
.trim()
: "sin sector";
};
const fetchAllFeatures = async (layer) => {
const query = layer.query();
let allFeatures = [];
const getPage = async (query, response = null) => {
return new Promise((resolve, reject) => {
query.run((error, featureCollection, nextResponse) => {
if (error) return reject(error);
allFeatures = [...allFeatures, ...featureCollection.features];
if (nextResponse?.properties?.exceededTransferLimit) {
query.next(nextResponse, (err, nextFeatureCollection, nextNextResponse) => {
if (err) return reject(err);
resolve(getPage(query, nextNextResponse));
});
} else {
resolve(allFeatures);
}
});
});
};
return getPage(query);
};
const setupMap = () => {
const baseLayers = {
"OpenStreetMap": L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "Map data © OpenStreetMap contributors",
}),
"IGN Base": L.tileLayer.wms("https://www.ign.es/wms-inspire/ign-base?", {
layers: "IGNBaseTodo",
format: "image/png",
transparent: true,
opacity: 1,
attribution: '© <a href="http://www.ign.es/">Instituto Geográfico Nacional de España</a>',
}),
"Ortofoto PNOA": L.tileLayer.wms("https://www.ign.es/wms-inspire/pnoa-ma?", {
layers: "OI.OrthoimageCoverage",
format: "image/jpeg",
attribution: "PNOA - © Instituto Geográfico Nacional de España",
}),
"Ortofoto GVA 2024": L.tileLayer.wms("https://terramapas.icv.gva.es/0202_2024CVAL0025?SERVICE=WMS", {
layers: "2024CVAL0025_RGB",
format: "image/png",
attribution: '© <a href="https://visor.gva.es/visor/?capasids=Orto_2024;&nodoDesplegado=Orto_2024&idioma=es">Institut Cartogràfic Valencià</a>',
}),
};
map.value = L.map("map", {
center: [39.470239, -0.376805],
zoom: 13,
fullscreenControl: true,
layers: [baseLayers["OpenStreetMap"]],
});
L.control.layers(baseLayers, {}).addTo(map.value);
};
const setupFallasLayer = async () => {
const fallasLayer = esriLeaflet.featureLayer({
url: "https://geoportal.valencia.es/server/rest/services/CulturaFestiva/FallasPublicoV2/MapServer/2"
});
try {
const allFeatures = await fetchAllFeatures(fallasLayer);
console.log("Total de Features obtenidas:", allFeatures.length);
fallasPorSector = allFeatures.reduce((acc, falla) => {
const sectorOriginal = falla.properties.sector || "Sin Sector";
const sectorNormalizado = normalizeSectorName(sectorOriginal);
if (!acc[sectorNormalizado]) {
acc[sectorNormalizado] = {
nombreOriginal: sectorOriginal,
fallas: [],
};
}
acc[sectorNormalizado].fallas.push(falla);
return acc;
}, {});
Object.entries(fallasPorSector).forEach(([sectorNormalizado, datosSector]) => {
const {nombreOriginal, fallas} = datosSector;
const layerGroup = L.layerGroup();
fallas.forEach(falla => {
if (falla.geometry && Array.isArray(falla.geometry.coordinates)) {
const [lon, lat] = falla.geometry.coordinates;
if (typeof lat === 'number' && typeof lon === 'number') {
const seccion = falla.properties.seccion || "Sin Sección";
const iconUrl = seccion === "E" ? specialSectionIconBase64 : generateNumberIcon(seccion);
const marker = L.marker([lat, lon], {
icon: L.icon({
iconUrl: iconUrl,
iconSize: seccion === "E" ? [48, 48] : [24, 24],
iconAnchor: seccion === "E" ? [24, 48] : [12, 24],
popupAnchor: [1, -34],
}),
});
marker.on("click", () => {
emit("falla-seleccionada", falla.properties);
});
layerGroup.addLayer(marker);
}
}
});
capasPorSector[sectorNormalizado] = layerGroup;
});
const capasConNombresOriginales = Object.entries(fallasPorSector).reduce((acc, [sectorNormalizado, datosSector]) => {
acc[datosSector.nombreOriginal] = capasPorSector[sectorNormalizado];
return acc;
}, {});
const layersControl = L.control.layers(null, capasConNombresOriginales, {collapsed: true}).addTo(map.value);
const layersControlContainer = layersControl.getContainer();
const customIcon = document.createElement('div');
customIcon.innerHTML = 'Sector';
customIcon.style.backgroundColor = '#4CAF50';
customIcon.style.color = 'white';
customIcon.style.width = '42px';
customIcon.style.height = '42px';
customIcon.style.display = 'flex';
customIcon.style.alignItems = 'center';
customIcon.style.justifyContent = 'center';
customIcon.style.fontWeight = 'bold';
const defaultIcon = layersControlContainer.querySelector('.leaflet-control-layers-toggle');
if (defaultIcon) {
defaultIcon.replaceWith(customIcon);
}
Object.values(capasPorSector).forEach(layer => layer.addTo(map.value));
}
catch (error) {
console.error("Error obteniendo features:", error);
}
};
onMounted(() => {
setupMap();
setupFallasLayer();
});
</script>
<style>
#map {
height: 100vh;
width: 100%;
}
.custom-layer-icon {
margin-right: 5px;
}
</style>
A continuación, desglosaremos el código y explicaremos cada parte en detalle.
1. Importaciones
import { ref, onMounted, defineEmits } from "vue";
import L from "leaflet";
import "leaflet-fullscreen";
import "leaflet/dist/leaflet.css";
import * as esriLeaflet from "esri-leaflet";
Explicación:
ref
,onMounted
,defineEmits
: Funciones de VueJS para crear variables reactivas, ejecutar código después de montar el componente y definir eventos personalizados.L
: La biblioteca Leaflet , que proporciona herramientas para crear mapas interactivos.leaflet-fullscreen
: Plugin para añadir un control de pantalla completa al mapa.esri-leaflet
: Plugin para cargar capas geoespaciales desde ArcGIS (en este caso, las ubicaciones de las fallas).
2. Estado Reactivo
const map = ref(null);
const emit = defineEmits(["falla-seleccionada"]);
Explicación:
map
: Una variable reactiva que almacenará la instancia del mapa de Leaflet.emit
: Define un evento personalizado (falla-seleccionada
) que se usará para notificar al componente padre (App.vue
) cuando el usuario seleccione una falla.
3. Métodos o funciones Auxiliares
En este componente se han desarrollado varias funciones o métodos auxiliares, que ayudan, en nuestro caso, con la generación de iconos.
3.1. Generar Colores Dinámicos
const getColorForSection = (seccion) => {
const sectionNumber = parseInt(seccion.replace(/\D/g, ""));
if (isNaN(sectionNumber)) return "#000000";
const red = Math.min(255, 255 - (sectionNumber - 1) * 30);
const green = Math.min(255, (sectionNumber - 1) * 30);
return `rgb(${red}, ${green}, 0)`;
};
Explicación:
- Esta función genera colores únicos basados en el número de la sección de la falla:
- Secciones más bajas → Rojo (
rgb(255, 0, 0)
). - Secciones más altas → Verde (
rgb(0, 255, 0)
).
- Secciones más bajas → Rojo (
- Esto permite diferenciar visualmente las fallas según su sección.
3.2. Generar Iconos Personalizados
Este método genera el icono de una sección. Cada falla pertenece a una sección y según su calidad, materiales y detalles, pasan de Fuera de Concurso (representada por FC), sección especial – representada por un icono especial y con mayor tamaño que los demás – y después ya iconos desde primera a octava sección A,B y C. El color va en escala del rojo brillante de la sección primera hacia el verde de las secciones últimas. Este método genera un icono con el color y nombre de la sección, listo para representar la ubicación de una falla en el mapa.

const generateNumberIcon = (seccion, isLarge = false) => {
const size = isLarge ? 48 : 24;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
ctx.fillStyle = getColorForSection(seccion);
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#FFFFFF";
ctx.font = `bold ${size / 2}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(seccion, size / 2, size / 2);
return canvas.toDataURL();
};
3.3 Normalizar los nombres de los sectores
El método normalizeSectorName
es una función auxiliar que normaliza los nombres de los sectores para evitar inconsistencias, errores tipográficos o variaciones en el formato de los nombres. Esto es especialmente útil cuando se trabaja con datos geoespaciales o información proporcionada por fuentes externas, ya que asegura que los nombres de los sectores sean consistentes y estandarizados.
Propósito y Beneficios
- Manejo de Datos Erróneos: Proporciona un valor predeterminado (
"sin sector"
) para casos en los que no se proporcione un nombre válido. - Consistencia: Asegura que los nombres de los sectores sean uniformes, independientemente de cómo se escriban originalmente.
- Evitar Duplicados: Reduce la posibilidad de tener nombres duplicados debido a diferencias en mayúsculas, acentos o espacios.
- Facilitar Agrupación: Permite agrupar correctamente las fallas por sector, ya que los nombres normalizados son únicos y consistentes.
const normalizeSectorName = (text) => {
return text
? text
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/\s+/g, " ")
.trim()
: "sin sector";
};
4. Inicialización del mapa
La función setupMap
es responsable de configurar e inicializar el mapa interactivo en la aplicación. Realiza las siguientes tareas clave:
- Define Capas Base: Crea un conjunto de capas base que los usuarios pueden alternar para cambiar la visualización del mapa.
- Inicializa el Mapa: Configura el mapa con coordenadas centradas en València, un nivel de zoom adecuado y una capa base predeterminada (OpenStreetMap).
- Añade Control de Capas: Proporciona un control en la interfaz que permite a los usuarios seleccionar entre las diferentes capas base disponibles.
Esta función nos permite inicializar el mapa en las coordenadas 39.470239, -0.376805 y con zoom 13, centrada en la ciudad de València.
Lista de Capas del Mapa
- OpenStreetMap
- Descripción: Mapa estándar con calles, nombres y detalles generales.
- Fuente: OpenStreetMap.
- Formato:
image/png
. - Uso: Ideal para una vista general del mapa.
- IGN Base
- Descripción: Mapa topográfico proporcionado por el Instituto Geográfico Nacional de España (IGN).
- Capas:
"IGNBaseTodo"
(todas las capas disponibles). - Formato:
image/png
. - Uso: Útil para visualizar detalles geográficos como relieve, carreteras y límites administrativos.
- Ortofoto PNOA
- Descripción: Ortofotografía nacional del Plan Nacional de Ortofotografía Aérea (PNOA).
- Capas:
"OI.OrthoimageCoverage"
(cobertura de imágenes ortográficas). - Formato:
image/jpeg
. - Uso: Proporciona imágenes satelitales detalladas del terreno.
- Ortofoto GVA 2024
- Descripción: Ortofotografía reciente (2024) de la Comunidad Valenciana.
- Capas:
"2024CVAL0025_RGB"
(imágenes en color). - Formato:
image/png
. - Uso: Ofrece una vista actualizada y detallada de la Comunitat Valenciana.
const setupMap = () => {
const baseLayers = {
"OpenStreetMap": L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "Map data © OpenStreetMap contributors",
}),
"IGN Base": L.tileLayer.wms("https://www.ign.es/wms-inspire/ign-base?", {
layers: "IGNBaseTodo",
format: "image/png",
transparent: true,
opacity: 1,
attribution: '© <a href="http://www.ign.es/">Instituto Geográfico Nacional de España</a>',
}),
"Ortofoto PNOA": L.tileLayer.wms("https://www.ign.es/wms-inspire/pnoa-ma?", {
layers: "OI.OrthoimageCoverage",
format: "image/jpeg",
attribution: "PNOA - © Instituto Geográfico Nacional de España",
}),
"Ortofoto GVA 2024": L.tileLayer.wms("https://terramapas.icv.gva.es/0202_2024CVAL0025?SERVICE=WMS", {
layers: "2024CVAL0025_RGB",
format: "image/png",
attribution: '© <a href="https://visor.gva.es/visor/?capasids=Orto_2024;&nodoDesplegado=Orto_2024&idioma=es">Institut Cartogràfic Valencià</a>',
}),
};
map.value = L.map("map", {
center: [39.470239, -0.376805],
zoom: 13,
fullscreenControl: true,
layers: [baseLayers["OpenStreetMap"]],
});
L.control.layers(baseLayers, {}).addTo(map.value);
};
5. Inicialización de la capa de fallas
La función setupFallasLayer
es una función asíncrona que se encarga de cargar, procesar y organizar las ubicaciones de las fallas en el mapa. A continuación, desglosamos cada parte del código para entender su propósito y funcionalidad.
1. Cargar la Capa de Fallas
const fallasLayer = esriLeaflet.featureLayer({
url: "https://geoportal.valencia.es/server/rest/services/CulturaFestiva/FallasPublicoV2/MapServer/2"
});
Explicación:
Esta capa incluye todas las fallas con sus propiedades (nombre, sector, sección, etc.) y geometría (coordenadas).
Los datos se cargan desde https://geoportal.valencia.es/apps/GeoportalHome/es/inicio/fallas y https://geoportal.valencia.es/server/rest/services/CulturaFestiva/FallasPublicoV2/MapServer mediante la API REST de ArcGis.
L.esri.FeatureLayer
: Carga datos geoespaciales desde un servicio de mapas de ArcGIS.url
: URL del servicio que contiene las ubicaciones de las fallas.
2. Recuperar Todas las Features
const allFeatures = await fetchAllFeatures(fallasLayer);
console.log("Total de Features obtenidas:", allFeatures.length);
Explicación:
fetchAllFeatures
: Una función auxiliar que recupera todas las features (fallas) de la capa, manejando paginación si hay más de 100 elementos.- Resultado: Almacena todas las fallas en la variable
allFeatures
.
3. Agrupar Fallas por Sector
fallasPorSector = allFeatures.reduce((acc, falla) => {
const sectorOriginal = falla.properties.sector || "Sin Sector";
const sectorNormalizado = normalizeSectorName(sectorOriginal);
if (!acc[sectorNormalizado]) {
acc[sectorNormalizado] = {
nombreOriginal: sectorOriginal,
fallas: [],
};
}
acc[sectorNormalizado].fallas.push(falla);
return acc;
}, {});
Explicación:
fallas
: Lista de fallas pertenecientes a ese sector.reduce
: Agrupa las fallas por sector normalizado.sectorOriginal
: Nombre original del sector (puede contener errores tipográficos o variaciones).sectorNormalizado
: Versión estandarizada del nombre del sector (usando la funciónnormalizeSectorName
).
4. Crear Marcadores y Capas por Sector
Object.entries(fallasPorSector).forEach(([sectorNormalizado, datosSector]) => {
const { nombreOriginal, fallas } = datosSector;
const layerGroup = L.layerGroup();
fallas.forEach(falla => {
if (falla.geometry && Array.isArray(falla.geometry.coordinates)) {
const [lon, lat] = falla.geometry.coordinates;
if (typeof lat === 'number' && typeof lon === 'number') {
const seccion = falla.properties.seccion || "Sin Sección";
const iconUrl = seccion === "E" ? specialSectionIconBase64 : generateNumberIcon(seccion);
const marker = L.marker([lat, lon], {
icon: L.icon({
iconUrl: iconUrl,
iconSize: seccion === "E" ? [48, 48] : [24, 24],
iconAnchor: seccion === "E" ? [24, 48] : [12, 24],
popupAnchor: [1, -34],
}),
});
marker.on("click", () => {
emit("falla-seleccionada", falla.properties);
});
layerGroup.addLayer(marker);
}
}
});
capasPorSector[sectorNormalizado] = layerGroup;
});
Explicación:
- Iteración sobre Sectores:
- Para cada sector, crea un grupo de capas (
layerGroup
) que contendrá los marcadores de las fallas pertenecientes a ese sector.
- Para cada sector, crea un grupo de capas (
- Creación de Marcadores:
- Verifica que las coordenadas sean válidas (
typeof lat === 'number' && typeof lon === 'number
). - Crea un marcador con el ícono correspondiente (basado en la sección de la falla):
- Si la sección es
"E"
(Sección Especial), usa un ícono predefinido (specialSectionIconBase64
). - Para otras secciones, genera un ícono dinámico con
generateNumberIcon
.
- Si la sección es
- Añade un evento
click
al marcador para emitir un eventofalla-seleccionada
con los datos de la falla.
- Verifica que las coordenadas sean válidas (
- Almacenamiento de Capas:
- Guarda cada grupo de capas en
capasPorSector
usando el nombre normalizado del sector como clave.
- Guarda cada grupo de capas en
5. Crear Control de Capas Personalizado
const capasConNombresOriginales = Object.entries(fallasPorSector).reduce((acc, [sectorNormalizado, datosSector]) => {
acc[datosSector.nombreOriginal] = capasPorSector[sectorNormalizado];
return acc;
}, {});
const layersControl = L.control.layers(null, capasConNombresOriginales, { collapsed: true }).addTo(map.value);
const layersControlContainer = layersControl.getContainer();
const customIcon = document.createElement('div');
customIcon.innerHTML = 'Sector';
customIcon.style.backgroundColor = '#4CAF50';
customIcon.style.color = 'white';
customIcon.style.width = '42px';
customIcon.style.height = '42px';
customIcon.style.display = 'flex';
customIcon.style.alignItems = 'center';
customIcon.style.justifyContent = 'center';
customIcon.style.fontWeight = 'bold';
const defaultIcon = layersControlContainer.querySelector('.leaflet-control-layers-toggle');
if (defaultIcon) {
defaultIcon.replaceWith(customIcon);
}
Object.values(capasPorSector).forEach(layer => layer.addTo(map.value));
Explicación:
- Control de Capas:
- Crea un control de capas (
layersControl
) que permite activar/desactivar sectores específicos. - Usa los nombres originales de los sectores para etiquetar las capas.
- Crea un control de capas (
- Personalización del Botón:
- Reemplaza el botón predeterminado del control de capas con un diseño personalizado (
customIcon
). - El botón muestra la palabra «Sector» en verde, centrada y con estilo visualmente atractivo.
- Reemplaza el botón predeterminado del control de capas con un diseño personalizado (
- Añadir Capas al Mapa:
- Asegura que todas las capas (sectores) estén visibles inicialmente añadiéndolas al mapa.
Componente FallaInfo.vue
: Información de una Falla
El componente FallaInfo.vue
es responsable de mostrar información detallada sobre una falla seleccionada en el mapa. Este panel lateral proporciona una vista clara y organizada de los datos asociados a la falla, como su nombre, sector, sección, artistas, lema y otros detalles relevantes. Además, incluye iconos personalizados basados en la sección de la falla y un diseño limpio con estilos modernos.
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
falla: Object,
});
const emit = defineEmits(["close"]);
// Icono base64 para la Sección Especial
const specialSectionIconBase64 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEzklEQVRYhe2Zv2/aaBjHPyDSClVpe1Y9mEpdysQYGlaGDolUtxNDcnPLCaEM+QPiEPIHdIgiq2TqcmRgOF1SiQ4dvDqlww2evJ0wgyVfqyqKaCy4wTZHqAFD6FWn63eC930NH55f7/s+JPiPKPG9AaLqB+iidW3QNjwFsjGQ/aHs0HSnD60YWEAtBa15v2cuUAuyfXjqw2XjgshSOkNcEIkL4mBdz7GlS9OQXdMAKFrecM2FvQce/LcDtaAIvIoByfUCN1bz3Mzlpz7X1TXO6yquaRQTUGzD3n2oLBzUt2IFkJPrBZJrBRLpTNTHuZnzfpBrGpzXVbq6tmuB7MKzKNaNBOrH4auldEa6tVmKZMGxX5jOcGfnANc0+Li/lU04dtuCR9Pidypo24vF3xPpDLfLykxWnAYsvDzG2d6g59jvp8FOBLW8DH6VSGe4u3NwJVEWobggcu/1O5ztDVzTmAg7EbQPlaV0RrpdVhYOOay7OweBZU+AVNiasaB+dsu3NksLc/c4xQWR5bLCp/0tqQ2VsGoQChq4PLlemJo4HcemWldRy8q1YIOq0NW1XaKC+sWc5Fph7AcfNRse6F82p7rGykPvvZzLI80ZJstlha7+GAuKKahNBY35tXKcy4+aDUqH1StjwfvSIbgnf8wFGhdEkusFLpqNCtNAg737xur8tfI6Sq4VuGg2pDY8vQ8nwXiYRbNxQZypqEuCSMexQ+c6jj1TKARejIF0ZXx0YQzkpRmyXM7lebKap1pXB7ClwypqWaFaV/lgGqhlZWZY1zSKDLk/NEZnqZkr6Qwv1gscvW0MQDuOzVGzgfSTyPM1b+7FWiEy7JIHeqWejnV9VJ3q2qACBJIEkWw6gySItEyDU10DmAmWaa6PIjmXp1r34vKDd9YcSPm5hLJZGlQGtayQmhDDYQoz1FygkiDy5+t3VOsq1V9VwAsBZbOEnMtTOqwOrPzmTOO5X4/nra/jQDs9x5ZCxr+Sslli5WGG0mEV/eWx97AfnwHYqa5562ZI0J5n/c5E0D60Lk1DHh0fp5QgXrFUEI8B6Iv1AtW6ym87B5FBL02D/sgpKqw8We5I3M0iz/Xe65Qg8sE0ZnJ5z7FxTYPYyKk/zPU1oNjVtblO8kdvG4MNILCunMtzqmvIEe9WQxzjQVPQsoDzujoTaLDXj5Yq+Ccc3pxpPFnNTwT+cqYBdEYP0OOyvuaaRtE1jaln0aBmrjz01k077mUnfJ5rGoFFT0bnQkFd2EtA8byucmdKEkiCeO2zaKDzugpettdG50JBH4DVhr2uru1Gseoi1NW1gTXD7k1jC/59qFggf9zfygovj7/pnck1DT57Md5KwS9haybuTC48Szh229ne4N7rd9+CcdCQ6Dl2qw9749ZNBH0AlgWPeo793tneWPiVeahrQt9r8XyVRJFAYVCuHrmm8d7Z3mC5rFyrUxKoq2t8PqwOLDkJMhLoMGzPsU8+7W9JN3N5lue86w9bEYgEGRk0gAVSbah0dW23qz8marOs59h0dY0vZ4PM7uBld2jiXAs0kN8cqFhQvGg2KhfNhgTe9SG4wgSW7jk2l6bB0Nmhg2e9mZu6c3ec/Xt3zW+iSf5OluLqyfz7dpyHNRRfX+0mi9T/58+Gf0t/A+StDeWZzrw7AAAAAElFTkSuQmCC";
// Función para generar un color basado en la sección
const getColorForSection = (seccion) => {
const sectionNumber = parseInt(seccion.replace(/\D/g, ""));
if (isNaN(sectionNumber)) return "#000000"; // Si no es un número, devolver negro
// Escala de rojo (1B) a verde (8)
const red = Math.min(255, 255 - (sectionNumber - 1) * 30);
const green = Math.min(255, (sectionNumber - 1) * 30);
return `rgb(${red}, ${green}, 0)`;
};
// Función para generar un icono con un número en formato base64
const generateNumberIcon = (seccion, isLarge = false) => {
const size = isLarge ? 48 : 24; // Tamaño doble para "Primera A" y "Especial"
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
// Fondo del icono
ctx.fillStyle = getColorForSection(seccion); // Color basado en la sección
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fill();
// Número en el centro
ctx.fillStyle = "#FFFFFF"; // Texto blanco
ctx.font = `bold ${size / 2}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(seccion, size / 2, size / 2);
return canvas.toDataURL();
};
</script>
<template>
<div class="w-1/4 h-full bg-gray-100 p-4 relative">
<button @click="emit('close')" class="absolute top-2 right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center">X</button>
<div v-if="falla" class="mt-4 p-4 bg-white shadow-md rounded-lg">
<h2 class="text-xl font-bold mb-3">Falla {{ falla.nombre }}</h2>
<p><strong>Año de fundación:</strong> {{ falla.anyo_fundacion }}</p>
<p><strong>Artista:</strong> {{ falla.artista }}</p>
<p><strong>Distintivo:</strong> {{ falla.distintivo }}</p>
<p><strong>Fallera:</strong> {{ falla.fallera }}</p>
<p><strong>Lema:</strong> {{ falla.lema }}</p>
<p><strong>Presidente:</strong> {{ falla.presidente }}</p>
<p><strong>Sector:</strong> {{ falla.sector }}</p>
<div v-if="falla.seccion === 'E'" class="mt-2 flex items-center">
<img
:src="specialSectionIconBase64"
alt="Falla Sección Especial"
class="w-12 h-12 inline-block"
/>
<span class="ml-2 font-bold text-red-600">Sección Especial</span>
</div>
<div v-else-if="falla.seccion === '1A'" class="mt-2 flex items-center">
<img
:src="generateNumberIcon(falla.seccion, true)"
:alt="`Falla Sección ${falla.seccion}`"
class="w-12 h-12 inline-block"
/>
<span class="ml-2 font-bold">Sección {{ falla.seccion }}</span>
</div>
<div v-else class="mt-2 flex items-center">
<img
:src="generateNumberIcon(falla.seccion)"
:alt="`Falla Sección ${falla.seccion}`"
class="w-6 h-6 inline-block"
/>
<span class="ml-2 font-bold">Sección {{ falla.seccion }}</span>
</div>
<div v-if="falla.boceto" class="mt-4">
<img :src="falla.boceto" alt="Boceto de la Falla" class="w-full rounded-lg shadow-md" />
</div>
</div>
<p v-else>Selecciona una Falla en el mapa</p>
</div>
</template>
<style scoped>
.w-1\/4 {
width: 25%;
}
</style>
Fichero app.js, punto de entrada
El fichero app.js
es el punto de entrada principal de la aplicación VueJS. Es responsable de inicializar y montar la aplicación en el DOM, además de configurar cualquier dependencia global o estilo necesario.
import { createApp } from 'vue'
import "./assets/tailwind.css";
import App from './App.vue'
createApp(App).mount('#app')
Uniéndolo Todo
En este proyecto, hemos combinado tres tecnologías clave (VueJS 3, Leaflet y Tailwind CSS) para crear una experiencia interactiva que rinde homenaje a las Fallas de València. Cada una de estas herramientas desempeña un papel fundamental:
- VueJS 3 nos permite gestionar el estado de la aplicación de manera eficiente, facilitando la comunicación entre componentes y asegurando que la interfaz de usuario sea reactiva y dinámica. Gracias a su sistema de componentes, hemos podido dividir la aplicación en partes modulares y reutilizables, como el mapa interactivo (
MapView.vue
) y el panel de información (FallaInfo.vue
). - Leaflet ha sido la pieza central para la visualización geográfica. Con su capacidad para cargar capas base personalizadas, crear marcadores dinámicos y gestionar interacciones como el zoom y el clic, hemos logrado un mapa que no solo es funcional, sino también atractivo. La integración con ESRI Leaflet nos ha permitido acceder a datos geoespaciales actualizados y mostrar las ubicaciones de las fallas con precisión.
- Tailwind CSS ha sido el aliado perfecto para el diseño. Su enfoque basado en utilidades nos ha permitido crear una interfaz limpia y responsiva sin necesidad de escribir CSS personalizado. Además, su sistema de diseño responsivo ha asegurado que la aplicación se vea y funcione bien en cualquier dispositivo, desde móviles hasta pantallas de escritorio.
La combinación de estas tecnologías no solo ha hecho posible la creación de un mapa interactivo, sino que también ha demostrado cómo la tecnología puede enriquecer experiencias culturales y turísticas. Al unir la tradición centenaria de las Fallas con herramientas modernas, hemos creado una plataforma que permite a los usuarios explorar, aprender y planificar su visita de una manera innovadora.
Si deseas acceder al código fuente, puedes hacerlo a través del repositorio GIT https://github.com/andreums/leaflet_fallas además, puedes probar en directo el mapa https://leaflet-fallas-3kcw9tf8y-andreums-projects.vercel.app .
Conclusiones
Este proyecto ha sido una experiencia enriquecedora tanto a nivel técnico como personal. A través de la creación de este mapa interactivo de las Fallas de València, hemos logrado combinar tecnología moderna con una tradición cultural profundamente arraigada, demostrando que ambas pueden coexistir y potenciarse mutuamente.
A Nivel Técnico
Desde una perspectiva técnica, este proyecto ha sido un excelente ejemplo de cómo herramientas como VueJS 3, Leaflet y Tailwind CSS pueden trabajar juntas para crear aplicaciones web modernas, eficientes y visualmente atractivas. Hemos explorado conceptos avanzados como la gestión del estado reactivo, la visualización de datos geoespaciales y el diseño responsivo, lo que ha ampliado nuestro conocimiento y habilidades en el desarrollo frontend.
Además, la integración de datos geoespaciales a través de ESRI Leaflet ha sido un desafío técnico gratificante. Nos ha permitido no solo mostrar ubicaciones en un mapa, sino también organizar y presentar información de manera dinámica y accesible. Este enfoque puede aplicarse a otros proyectos, desde mapas turísticos hasta herramientas de planificación urbana, lo que abre un abanico de posibilidades para futuros desarrollos.
A Nivel Cultural
Las Fallas de València son una celebración única en el mundo, y este proyecto ha sido una forma de rendir homenaje a su riqueza artística y cultural. Al crear una herramienta que permite a los usuarios explorar las fallas y descubrir detalles sobre sus artistas y comisiones, estamos contribuyendo a la preservación y difusión de esta tradición. Este mapa no solo es útil para los visitantes, sino también para los valencianos, que pueden redescubrir su ciudad y sentirse orgullosos de su patrimonio cultural.
Además, este proyecto es un recordatorio de que la tecnología no tiene por qué estar reñida con la tradición. Al contrario, puede ser una herramienta poderosa para mantener vivas las tradiciones y acercarlas a nuevas generaciones. En un mundo cada vez más digital, proyectos como este demuestran que la cultura puede adaptarse y florecer en nuevos formatos.
A Nivel Personal
Para mí, este proyecto ha sido especialmente significativo. Como desarrollador y amante de la cultura valenciana, ha sido un placer unir mis dos pasiones en una sola aplicación. Cada línea de código, cada marcador en el mapa y cada detalle de diseño han sido creados con el objetivo de transmitir la magia de las Fallas. Ver cómo el mapa cobra vida y cómo los usuarios pueden interactuar con él ha sido increíblemente gratificante.
Este proyecto también me ha enseñado la importancia de crear herramientas que no solo sean funcionales, sino también significativas. La tecnología no es un fin en sí misma, sino un medio para conectar personas, contar historias y enriquecer experiencias. Este mapa es, en esencia, una invitación a explorar, aprender y celebrar la cultura valenciana.
Mirando hacia el Futuro
Este mapa interactivo es solo el comienzo. En el futuro, podríamos añadir funcionalidades como rutas personalizadas, integración con redes sociales para compartir experiencias, o incluso realidad aumentada para visualizar las fallas en 3D. Las posibilidades son infinitas, y este proyecto es una prueba de que, cuando la tecnología y la cultura se unen, el resultado puede ser verdaderamente mágico.
En definitiva, este proyecto es un homenaje a las Fallas, a València y a la capacidad de la tecnología para enriquecer nuestras vidas. Espero que este mapa no solo sea útil, sino que también inspire a otros a explorar, valorar y celebrar las tradiciones culturales que nos hacen únicos. ¡Visca les Falles y visca la tecnologia!