wikiSistema de Eventos - Documentación Técnica

Sistema de Eventos - Documentación Técnica Completa

📋 Tabla de Contenidos

  1. Visión General
  2. Arquitectura del Sistema
  3. Backend - APIs y Base de Datos
  4. Frontend - Componentes
  5. Flujo End-to-End
  6. Actualización Automática
  7. Especificaciones Técnicas

Visión General

El sistema de eventos de Makers of Murcia es un sistema completamente autónomo que obtiene, almacena y muestra eventos desde Meetup sin intervención humana. El sistema combina múltiples fuentes de datos y se actualiza automáticamente.

Características Principales

  • ✅ Obtención automática de eventos futuros desde GraphQL API de Meetup
  • ✅ Obtención automática de eventos pasados desde RSS Feed de Meetup
  • ✅ Almacenamiento en base de datos PostgreSQL con Prisma ORM
  • ✅ Actualización mensual automática mediante cron job
  • ✅ Fallback a JSON estático cuando la base de datos está vacía
  • ✅ Mismo comportamiento en local y producción
  • ✅ Sin datos mockup - solo eventos reales

Arquitectura del Sistema

Diagrama de Flujo

┌─────────────────────────────────────────────────────────────┐
│                    FUENTES DE DATOS                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐      ┌──────────────┐                    │
│  │  GraphQL API │      │   RSS Feed   │                    │
│  │  (Futuros)   │      │   (Pasados)   │                    │
│  └──────┬───────┘      └──────┬───────┘                    │
│         │                      │                            │
│         └──────────┬───────────┘                            │
│                    │                                        │
│                    ▼                                        │
│         ┌──────────────────────┐                          │
│         │  /api/populate-events │                          │
│         │  (Guarda en BD)       │                          │
│         └──────────┬───────────┘                            │
│                    │                                        │
│                    ▼                                        │
│         ┌──────────────────────┐                          │
│         │   PostgreSQL (Prisma)│                          │
│         │   Tabla: cached_events│                          │
│         └──────────┬───────────┘                            │
│                    │                                        │
└────────────────────┼────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                    API UNIFICADA                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│         ┌──────────────────────┐                          │
│         │   /api/events         │                          │
│         │                       │                          │
│         │ 1. GraphQL (futuros)  │                          │
│         │ 2. BD Cache (pasados) │                          │
│         │ 3. JSON Fallback     │                          │
│         │                       │                          │
│         │ → Combina y normaliza │                          │
│         │ → Elimina duplicados │                          │
│         │ → Ordena cronológicamente                        │
│         └──────────┬───────────┘                            │
│                    │                                        │
└────────────────────┼────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                    COMPONENTES FRONTEND                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐      ┌──────────────┐                    │
│  │  Event.tsx   │      │EventsArchive │                    │
│  │  (Página     │      │.tsx          │                    │
│  │   raíz)      │      │(/events)     │                    │
│  └──────────────┘      └──────────────┘                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Backend - APIs y Base de Datos

1. API Unificada: /api/events

Archivo: app/api/events/route.ts

Función: Endpoint principal que combina todas las fuentes de eventos y las normaliza.

Parámetros de Query:

  • limit (opcional): Número máximo de eventos. Por defecto: todos los disponibles
  • status (opcional): Filtrar por estado ("all" | "past" | "upcoming"). Por defecto: "all"
  • group (opcional): URL del grupo de Meetup. Por defecto: "makers-of-murcia"

Flujo de Ejecución:

  1. Obtiene eventos futuros desde GraphQL API (en paralelo)
  2. Obtiene eventos pasados desde base de datos (en paralelo)
  3. Obtiene eventos pasados desde JSON estático como fallback (en paralelo)
  4. Combina todos los eventos
  5. Normaliza fechas y formatos
  6. Elimina duplicados (priorizando GraphQL sobre JSON)
  7. Ordena cronológicamente (más reciente primero)
  8. Aplica límite si se especifica
  9. Devuelve respuesta JSON con todos los eventos

Respuesta:

{
  success: true,
  events: ProcessedEvent[],
  count: number,
  source: 'graphql+json' | 'graphql+cache',
  stats: {
    future: number,
    past: number,
    total: number
  }
}

Código Clave:

// app/api/events/route.ts
// Obtener eventos futuros desde GraphQL
const futureEventsPromise = fetchEventsFromGraphQL(groupUrlname, limit)
 
// Obtener eventos pasados desde la base de datos (actualizada automáticamente por cron)
const pastEventsFromCachePromise = getPastEventsFromCache(limit)
const pastEventsFromJSONPromise = loadPastEventsFromJSON()
 
// Ejecutar todas las promesas en paralelo
const [futureEvents, pastEventsFromCache, pastEventsFromJSON] = await Promise.all([
  futureEventsPromise,
  pastEventsFromCachePromise,
  pastEventsFromJSONPromise
])
 
// Usar eventos de caché si hay, sino usar JSON como fallback
const pastEvents = pastEventsFromCache.length > 0 ? pastEventsFromCache : pastEventsFromJSON

2. Biblioteca Compartida: lib/meetup-api.ts

Archivo: lib/meetup-api.ts

Función: Funciones reutilizables para obtener eventos desde Meetup API.

fetchEventsFromGraphQL()

Obtiene eventos futuros desde la GraphQL API oficial de Meetup.

Endpoint: https://api.meetup.com/gql-ext

Autenticación: Opcional (mejora rate limits con MEETUP_API_TOKEN)

Limitación: GraphQL solo devuelve eventos futuros, NO eventos pasados

Características:

  • Solo devuelve eventos futuros (no pasados)
  • No requiere autenticación (opcional con MEETUP_API_TOKEN)
  • Timeout de 5 segundos
  • Normaliza fechas a formato ISO
  • Determina automáticamente el status (upcoming | past | cancelled)

Query GraphQL:

query GetGroupEvents($urlname: String!, $first: Int!) {
  groupByUrlname(urlname: $urlname) {
    id
    name
    urlname
    events(first: $first) {
      edges {
        node {
          id
          title
          description
          localTime
          localDate
          eventUrl
          photoUrl
          rsvpCount
          status
          venue {
            name
            address
            city
            state
            country
          }
          timezone
          duration
          group {
            name
          }
        }
      }
    }
  }
}

fetchEventsFromRSS()

Obtiene eventos pasados desde el RSS Feed de Meetup.

URLs intentadas (en orden):

  1. https://www.meetup.com/{group}/events/rss/
  2. https://www.meetup.com/es-ES/{group}/events/rss/
  3. https://meetup.com/{group}/events/rss/

Proceso:

  1. Intenta cada URL hasta encontrar una que funcione
  2. Parsea XML RSS usando regex
  3. Extrae: título, link, descripción, fecha
  4. Genera ID del evento desde la URL (/events/{id}/)
  5. Elimina duplicados por ID
  6. Devuelve array de eventos

Limitaciones:

  • RSS no incluye número de asistentes (se establece en 0)
  • RSS no incluye imágenes (usa placeholder de Unsplash)
  • Fechas se formatean a formato legible español

Interfaz ProcessedEvent:

interface ProcessedEvent {
  id: string
  title: string
  organizer: {
    name: string
    avatar: string
    subtitle: string
  }
  image: string
  date: string // ISO format: "2022-11-05T10:00:00+01:00"
  description: string
  link: string
  attendees: number
  status: string // "past" | "upcoming" | "cancelled"
  venue?: {
    name?: string
    address?: string
  }
  time?: string
  duration?: string
}

3. API de Población: /api/populate-events

Archivo: app/api/populate-events/route.ts

Función: Guarda eventos en la base de datos PostgreSQL.

Parámetros de Query:

  • group (opcional): URL del grupo. Por defecto: "makers-of-murcia"
  • limit (opcional): Número máximo de eventos a guardar. Por defecto: 20

Flujo de Ejecución:

  1. Obtiene eventos futuros desde GraphQL
  2. Obtiene eventos pasados desde RSS Feed
  3. Combina ambos tipos de eventos
  4. Elimina duplicados por ID
  5. Borra todos los eventos existentes en la base de datos (transacción)
  6. Inserta nuevos eventos en la base de datos
  7. Devuelve confirmación con el número de eventos guardados

Código Clave:

// app/api/populate-events/route.ts
// Obtener eventos futuros desde GraphQL
const futureEvents = await fetchEventsFromGraphQL(groupUrlname, limit)
 
// Obtener eventos pasados desde RSS (GraphQL solo devuelve futuros)
const pastEventsFromRSS = await fetchEventsFromRSS(groupUrlname)
 
// Combinar eventos futuros + pasados
let events = [...futureEvents, ...pastEventsFromRSS]
 
// Guardar en base de datos
await prisma.$transaction(async (tx: any) => {
  // Borrar todos los eventos existentes
  const deleted = await tx.cachedEvent.deleteMany({})
  
  // Insertar nuevos eventos
  for (const event of eventsToSave) {
    await tx.cachedEvent.create({
      data: {
        id: event.id,
        title: event.title,
        organizer: JSON.stringify(event.organizer),
        image: event.image,
        date: event.date,
        description: event.description,
        link: event.link,
        attendees: event.attendees,
        status: event.status,
        venue: event.venue ? JSON.stringify(event.venue) : null,
        time: event.time || undefined,
        duration: event.duration || undefined,
      }
    })
  }
})

4. Cron Job: /api/cron/update-events-cache

Archivo: app/api/cron/update-events-cache/route.ts

Función: Actualiza automáticamente la caché de eventos periódicamente.

Configuración en Vercel (vercel.json):

{
  "crons": [
    {
      "path": "/api/cron/update-events-cache",
      "schedule": "0 0 1 * *"
    }
  ]
}

Frecuencia: El día 1 de cada mes a las 00:00 UTC

Seguridad:

  • Requiere header x-vercel-cron (automático en Vercel)
  • O Authorization: Bearer {CRON_SECRET} para llamadas manuales

Flujo de Ejecución:

  1. Verifica autorización (header de Vercel o secret)
  2. Llama a /api/events para obtener todos los eventos
  3. Llama a /api/populate-events para guardarlos en la base de datos
  4. Devuelve confirmación con estadísticas

Código Clave:

// app/api/cron/update-events-cache/route.ts
// Obtener eventos desde la API unificada
const response = await fetch(
  `${baseUrl}/api/events?group=makers-of-murcia&limit=200&status=all`
)
 
// Guardar eventos en base de datos
const populateResponse = await fetch(
  `${baseUrl}/api/populate-events?group=makers-of-murcia&limit=200`,
  {
    headers: {
      'Authorization': request.headers.get('authorization') || ''
    }
  }
)

5. Base de Datos: Prisma Schema

Archivo: prisma/schema.prisma

Modelo: CachedEvent

model CachedEvent {
  id          String   @id // ID del evento de Meetup
  title       String
  organizer   String   // JSON string: {name, avatar, subtitle}
  image       String
  date        String
  description String
  link        String
  attendees   Int
  status      String
  venue       String?  // JSON string opcional: {name?, address?}
  time        String?
  duration    String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
 
  @@index([updatedAt])
  @@map("cached_events")
}

Características:

  • PostgreSQL como base de datos
  • Campos JSON serializados como strings (organizer, venue)
  • Índice en updatedAt para consultas ordenadas
  • id es el ID del evento de Meetup (clave primaria)

6. Fallback: JSON Estático

Archivo: data/scraped-events.json

Función: Archivo JSON con 77 eventos pasados scrapeados previamente.

Uso: Se utiliza como fallback cuando la base de datos está vacía.

Estructura: Array de objetos ProcessedEvent

Carga: Se lee desde el sistema de archivos en tiempo de ejecución.


Frontend - Componentes

1. Componente Event (Página Raíz)

Archivo: components/event.tsx

Ubicación: Sección “Eventos anteriores” en la página raíz (/)

Función: Muestra un carrusel horizontal de eventos pasados.

Características:

  • Carrusel con scroll horizontal
  • Navegación con flechas (visibles desde 640px en adelante)
  • Una card a la vez en mobile, múltiples en desktop
  • Cards con efecto 3D al hacer hover
  • Card “Ver todos” que redirige a /events

API que consume: /api/events?limit=200&status=all

Timeout: 10 segundos en desarrollo, 5 segundos en producción

Reintento automático: Si hay timeout, reintenta con 15 segundos

Código Clave:

// components/event.tsx
const loadEvents = async () => {
  try {
    const fetchTimeout = isDevelopment ? 10000 : 5000
    const response = await fetch('/api/events?limit=200&status=all', {
      signal: AbortSignal.timeout(fetchTimeout)
    })
    
    const data = await response.json()
    
    if (data.success && data.events && data.events.length > 0) {
      setEvents(data.events)
      setError(null)
    }
  } catch (err) {
    // Reintento automático si es timeout
    if (errorMessage.includes('timeout')) {
      // Reintentar con timeout más largo
    }
  }
}

Formato de Fecha: Usa formatEventDate() para mostrar fechas en formato legible (“5 nov 2022”)


2. Componente EventsArchive (Página /events)

Archivo: components/events-archive.tsx

Ubicación: Página /events

Función: Muestra todos los eventos en un grid responsive.

Características:

  • Grid de 1 columna en mobile, 2 columnas en desktop
  • Cards con efecto 3D al hacer hover
  • Muestra contador de eventos encontrados
  • Estados de carga (skeleton) y error

API que consume: /api/events?limit=200&status=all

Timeout: 5 segundos

Código Clave:

// components/events-archive.tsx
const response = await fetch('/api/events?limit=200&status=all', {
  signal: AbortSignal.timeout(5000)
})
 
const data = await response.json()
 
if (data.success && data.events && data.events.length > 0) {
  setEvents(data.events)
  setError(null)
}

Formato de Fecha: Usa formatEventDate() para mostrar fechas en formato legible


Flujo End-to-End

Flujo Completo: Desde Meetup hasta la UI

1. MEETUP.COM
   │
   ├─ GraphQL API (eventos futuros)
   │  └─→ fetchEventsFromGraphQL()
   │
   └─ RSS Feed (eventos pasados)
      └─→ fetchEventsFromRSS()

2. API /api/populate-events
   │
   └─→ Guarda en PostgreSQL (Prisma)
      └─→ Tabla: cached_events

3. API /api/events (unificada)
   │
   ├─→ Lee desde GraphQL (futuros)
   ├─→ Lee desde BD (pasados)
   └─→ Fallback a JSON (si BD vacía)
   │
   └─→ Combina, normaliza, ordena
      └─→ Devuelve JSON unificado

4. COMPONENTES FRONTEND
   │
   ├─→ Event.tsx (página raíz)
   └─→ EventsArchive.tsx (/events)
   │
   └─→ Renderiza cards con eventos reales

Flujo de Actualización Automática

CRON JOB (Mensual - día 1, 00:00 UTC)
   │
   ├─→ /api/cron/update-events-cache
   │  │
   │  ├─→ Llama a /api/events
   │  │  └─→ Obtiene eventos desde GraphQL + RSS
   │  │
   │  └─→ Llama a /api/populate-events
   │     └─→ Guarda en base de datos
   │
   └─→ Base de datos actualizada
      └─→ Próximas consultas usan BD en lugar de JSON

Actualización Automática

Cron Job Mensual

Configuración: vercel.json

{
  "crons": [
    {
      "path": "/api/cron/update-events-cache",
      "schedule": "0 0 1 * *"
    }
  ]
}

Frecuencia: El día 1 de cada mes a las 00:00 UTC

Qué hace:

  1. Obtiene eventos futuros desde GraphQL
  2. Obtiene eventos pasados desde RSS
  3. Guarda todo en la base de datos
  4. Reemplaza eventos antiguos con nuevos

Resultado: La base de datos se actualiza automáticamente cada mes con los eventos más recientes.

Crecimiento Automático

  • Eventos futuros: Se agregan automáticamente cuando se crean en Meetup (GraphQL en tiempo real)
  • Eventos pasados: Se agregan automáticamente cuando el cron job se ejecuta (mensualmente)
  • Sin intervención humana: El sistema es completamente autónomo

Especificaciones Técnicas

Stack Tecnológico

  • Backend: Next.js 14 API Routes
  • Base de Datos: PostgreSQL con Prisma ORM
  • APIs Externas:
    • Meetup GraphQL API (https://api.meetup.com/gql-ext)
    • Meetup RSS Feed (https://www.meetup.com/{group}/events/rss/)
  • Frontend: React 18, TypeScript, Tailwind CSS
  • Deployment: Vercel con Cron Jobs

Variables de Entorno

Nota: Estas variables deben configurarse en el entorno de producción (Vercel) y nunca deben exponerse públicamente.

# Opcional - mejora rate limits de Meetup API
MEETUP_API_TOKEN=<token_de_meetup>
 
# Requerido para cron jobs manuales (si se ejecutan fuera de Vercel)
CRON_SECRET=<secret_aleatorio_seguro>
 
# Base de datos PostgreSQL (URL de conexión)
DATABASE_URL=<url_de_conexion_postgresql>
 
# NextAuth (solo warnings, no afecta eventos)
NEXTAUTH_URL=<url_de_la_aplicacion>
NEXTAUTH_SECRET=<secret_aleatorio>
GITHUB_ID=<github_oauth_id>
GITHUB_SECRET=<github_oauth_secret>

Importante:

  • Estas variables se configuran en Vercel Dashboard → Settings → Environment Variables
  • Nunca deben aparecer en el código fuente ni en documentación pública con valores reales
  • Los valores mostrados aquí son solo ejemplos de qué variables se necesitan

Formatos de Datos

Fecha: ISO 8601 ("2022-11-05T10:00:00+01:00") Fecha mostrada: Formato legible español ("5 nov 2022") Status: "past" | "upcoming" | "cancelled"

Límites y Timeouts

  • GraphQL: Timeout de 5 segundos
  • RSS: Sin timeout específico (usa fetch default)
  • Frontend fetch: 10 segundos en desarrollo, 5 en producción
  • Reintento: 15 segundos si hay timeout
  • Límite de eventos: Por defecto todos (sin límite), máximo 200 por fuente

Orden de Prioridad

  1. Eventos futuros: GraphQL (siempre la fuente más actualizada)
  2. Eventos pasados: Base de datos (actualizada automáticamente)
  3. Fallback: JSON estático (solo si BD vacía)

Eliminación de Duplicados

  • Se eliminan eventos con el mismo id
  • Se prioriza GraphQL sobre JSON/BD cuando hay duplicados
  • Se mantiene el primero encontrado, se descarta el segundo

Páginas y Componentes que Usan Eventos

1. Página Raíz (/)

Archivo: app/page.tsx Componente usado: <Event /> desde components/event.tsx Sección: “Eventos anteriores” Datos: Eventos pasados en formato carrusel horizontal API: /api/events?limit=200&status=all Características:

  • Carrusel con scroll horizontal
  • Una card a la vez en mobile, múltiples en desktop
  • Navegación con flechas (visibles desde 640px)
  • Card “Ver todos” que redirige a /events

Código de integración:

// app/page.tsx
import Event from "@/components/event"
 
export default function Home() {
  return (
    <>
      <Header currentPath="/" />
      <Hero />
      <main>
        <Event /> {/* Sección "Eventos anteriores" */}
        <Testimonials />
        <ContactForm />
      </main>
    </>
  )
}

2. Página /events

Archivo: app/events/page.tsx Componente usado: <EventsArchive /> desde components/events-archive.tsx Datos: Todos los eventos en grid responsive API: /api/events?limit=200&status=all Características:

  • Grid de 1 columna en mobile, 2 columnas en desktop
  • Cards con efecto 3D al hacer hover
  • Contador de eventos encontrados
  • Estados de carga y error

Código de integración:

// app/events/page.tsx
import EventsArchive from "@/components/events-archive"
 
export default function EventsPage() {
  return (
    <Layout currentPath="/events">
      <section>
        <EventsArchive />
      </section>
    </Layout>
  )
}

Componentes Frontend - Detalles Técnicos

EventCard (Página Raíz)

Estructura de la Card:

  • Altura total: 226px (186px imagen + 40px barra de título)
  • Imagen: 186px de alto, full width
  • Overlay: Gradiente negro semitransparente con fecha y asistentes
  • Barra inferior: 40px de alto con título del evento
  • Efecto 3D: Aplicado con use3DCardEffect hook

Datos mostrados:

  • Fecha formateada: formatEventDate(event.date) → “5 nov 2022”
  • Asistentes: 👥 {event.attendees}
  • Título: Truncado con ellipsis si es muy largo

Responsive:

  • Mobile: w-full (una card a la vez, full width)
  • Desktop: md:w-[292px] (múltiples cards visibles)

EventCardArchive (Página /events)

Estructura de la Card:

  • Altura total: 208px (168px imagen + 40px barra de título)
  • Grid: 1 columna en mobile, 2 columnas en desktop
  • Click: Abre el link del evento en nueva pestaña

Datos mostrados:

  • Mismo formato que EventCard
  • Sin efecto carrusel, solo grid estático

Funciones de Utilidad

formatEventDate()

Archivo: lib/date-utils.ts

Función: Convierte fecha ISO a formato legible en español.

Input: "2022-11-05T10:00:00+01:00" Output: "5 nov 2022"

Código:

// lib/date-utils.ts
export function formatEventDate(dateISO: string): string {
  const date = new Date(dateISO)
  return date.toLocaleDateString('es-ES', {
    day: 'numeric',
    month: 'short',
    year: 'numeric'
  })
}

Flujos de Datos Detallados

Flujo 1: Carga Inicial de Eventos (Frontend)

Usuario visita página
   │
   ▼
Componente monta (useEffect)
   │
   ▼
fetch('/api/events?limit=200&status=all')
   │
   ├─ Timeout: 10s (dev) / 5s (prod)
   │
   ▼
API /api/events ejecuta:
   │
   ├─→ fetchEventsFromGraphQL() [paralelo]
   ├─→ getPastEventsFromCache() [paralelo]
   └─→ loadPastEventsFromJSON() [paralelo]
   │
   ▼
Combina y normaliza eventos
   │
   ▼
Devuelve JSON con eventos
   │
   ▼
Componente recibe eventos
   │
   ▼
setEvents(data.events)
   │
   ▼
Renderiza cards con eventos reales

Flujo 2: Actualización Automática (Cron)

Vercel Cron (día 1, 00:00 UTC)
   │
   ▼
GET /api/cron/update-events-cache
   │
   ├─ Verifica autorización (x-vercel-cron header)
   │
   ▼
Llama a /api/events
   │
   ├─→ Obtiene eventos futuros (GraphQL)
   └─→ Obtiene eventos pasados (RSS)
   │
   ▼
Llama a /api/populate-events
   │
   ├─→ Borra eventos antiguos de BD
   └─→ Inserta nuevos eventos en BD
   │
   ▼
Base de datos actualizada
   │
   ▼
Próximas consultas usan BD en lugar de JSON

Orden de Prioridad de Fuentes

Cuando /api/events se ejecuta, sigue este orden:

  1. Eventos futuros: Siempre desde GraphQL (más actualizado)
  2. Eventos pasados:
    • Primero intenta base de datos (si tiene eventos)
    • Si BD vacía, usa JSON estático como fallback
  3. Combinación: Une ambos tipos, elimina duplicados, ordena

Ejemplo:

GraphQL devuelve: 2 eventos futuros
BD devuelve: 50 eventos pasados
JSON tiene: 77 eventos pasados

Resultado: 2 futuros + 50 pasados (de BD) = 52 eventos totales

Normalización de Datos

Normalización de Fechas

Todas las fechas se convierten a formato ISO:

// Input: "5 nov 2022" o "2022-11-05"
// Output: "2022-11-05T10:00:00+01:00"
 
function normalizeEvent(event: ProcessedEvent): ProcessedEvent {
  let dateISO = event.date
  if (!event.date.includes('T') && !event.date.includes('Z')) {
    const parsedDate = new Date(event.date)
    if (!isNaN(parsedDate.getTime())) {
      dateISO = parsedDate.toISOString()
    }
  }
  return { ...event, date: dateISO }
}

Determinación de Status

El status se determina automáticamente si no está definido:

let status = event.status
if (!status) {
  const eventDate = new Date(dateISO)
  status = eventDate < new Date() ? 'past' : 'upcoming'
}

Manejo de Errores

En la API

  • GraphQL falla: Devuelve array vacío, continúa con otras fuentes
  • BD falla: Usa JSON como fallback automáticamente
  • JSON falla: Devuelve array vacío, pero no rompe la API
  • Error general: Devuelve { success: false, events: [] } con status 500

En el Frontend

  • Timeout: Reintenta automáticamente con timeout más largo (15s)
  • Error HTTP: Muestra mensaje de error, no datos mockup
  • Sin eventos: Muestra “No hay eventos disponibles”
  • Carga: Muestra skeleton mientras carga

Resumen Ejecutivo

El sistema de eventos es completamente autónomo y funciona de la siguiente manera:

  1. Obtención: GraphQL (futuros) + RSS (pasados)
  2. Almacenamiento: PostgreSQL con Prisma
  3. Actualización: Cron job mensual automático
  4. Visualización: Componentes React que consumen API unificada
  5. Fallback: JSON estático si la BD está vacía

No requiere intervención humana - los eventos se actualizan automáticamente cada mes y los futuros se obtienen en tiempo real.

Puntos Clave

  • ✅ Sistema autónomo: Sin intervención humana necesaria
  • ✅ Mismo comportamiento en local y producción: Usa las mismas APIs y fuentes
  • ✅ Solo eventos reales: No hay datos mockup en producción
  • ✅ Actualización automática: Cron job mensual actualiza la BD
  • ✅ Crecimiento automático: Los eventos se agregan automáticamente
  • ✅ Fallback robusto: Si una fuente falla, usa otra automáticamente