feat: Implement event and questionnaire management features

- Added event creation page with form handling and validation.
- Introduced layout for event management with context provider.
- Created event listing page with active and recent events display.
- Developed questionnaire creation page linked to events.
- Implemented breadcrumb navigation for better user experience.
- Added reusable components for event and questionnaire cards.
- Integrated API calls for fetching and managing event data.
- Enhanced slugify utility for better URL handling.
- Established context for managing event state across components.
This commit is contained in:
miguel
2025-08-12 20:00:21 -06:00
parent 5b0f0ad6bf
commit be8f207ecd
35 changed files with 2091 additions and 642 deletions
+99 -69
View File
@@ -3,6 +3,7 @@ import ImageUploader from '@/components/banner-uploader';
import Input from '@/components/input';
import Select from '@/components/select';
import { CreateEventoType } from '@/types/create-evento';
import { tipoEventos } from '@/utils/arrays';
import { formatDateLocal } from '@/utils/date-utils';
import { commands } from '@uiw/react-md-editor';
import dynamic from 'next/dynamic';
@@ -24,80 +25,109 @@ export default function CreateEvento({
handleBannerChange,
}: CreateEventoProps) {
return (
<div className="row border p-3 rounded my-4">
<h2>Información del Evento</h2>
<p>
Completa la siguiente información para describir los detalles generales
del evento.
</p>
<div className="mt-2">
<div className="p-4 border rounded bg-light">
<h2 className="mb-2">Creación del Evento</h2>
<p className="mb-0 text-muted">
Completa la siguiente información para describir los detalles
generales del evento.
</p>
<p className="mb-0 text-muted">
Presiona siguiente para continuar con la creacion del formulario.
</p>
</div>
<ImageUploader label="Banner del evento" onChange={handleBannerChange} />
<Input
label="Nombre del evento"
required
value={evento.nombre_evento}
onChange={(e) => handleChange('nombre_evento', e.target.value)}
/>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={evento.descripcion_evento}
onChange={(value) => {
const texto = value || '';
if (texto.length <= MAX_LENGTH) {
handleChange('descripcion_evento', texto);
}
}}
height={300}
commands={[commands.bold, commands.italic, commands.hr]}
/>
<div className="row mt-3">
{/* Columna Izquierda - Banner */}
<div className="col-lg-6">
<div className="pe-lg-3">
<ImageUploader
label="Banner del evento"
onChange={handleBannerChange}
/>
<p className="text-muted mt-2 small">
<strong>
<i className="bi bi-info-circle-fill me-2"></i>
Esta es la imagen que se verá en el carrusel del evento.
</strong>
<br />
<strong>
<i className="bi bi-image me-2"></i>
Dimensiones recomendadas: 1200x600 píxeles.
</strong>
<br />
<strong>
<i className="bi bi-file-earmark-image me-2"></i>
Formatos soportados: PNG, JPG, JPEG.
</strong>
</p>
</div>
</div>
</div>
<Select
label="Tipo de evento"
value={evento.tipo_evento}
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={[
{ label: 'Conferencia', value: 'conferencia' },
{ label: 'Taller', value: 'taller' },
{ label: 'Seminario', value: 'seminario' },
{ label: 'Curso', value: 'curso' },
{ label: 'Webinar', value: 'webinar' },
{ label: 'Reunión', value: 'reunion' },
{ label: 'Feria', value: 'feria' },
{ label: 'Concierto', value: 'concierto' },
{ label: 'Exposición', value: 'exposicion' },
{ label: 'Deportivo', value: 'deportivo' },
{ label: 'Cultural', value: 'cultural' },
{ label: 'Otro', value: 'otro' },
]}
/>
{/* Columna Derecha - Detalles del Evento */}
<div className="col-lg-6">
<div className="ps-lg-3">
<h4 className="mb-3">Detalles del Evento</h4>
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(evento.fecha_inicio)}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<Input
label="Nombre del evento"
required
value={evento.nombre_evento}
onChange={(e) => handleChange('nombre_evento', e.target.value)}
/>
<div className="col-md-6">
<Input
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(evento.fecha_fin)}
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
/>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={evento.descripcion_evento}
onChange={(value) => {
const texto = value || '';
if (texto.length <= MAX_LENGTH) {
handleChange('descripcion_evento', texto);
}
}}
height={200}
commands={[commands.bold, commands.italic, commands.hr]}
/>
</div>
</div>
<Select
label="Tipo de evento"
value={evento.tipo_evento}
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={tipoEventos}
/>
<div className="row">
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(evento.fecha_inicio)}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<Input
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(evento.fecha_fin)}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
+16 -3
View File
@@ -7,8 +7,11 @@ import {
import SimpleInput from '@/components/input';
import Select from '@/components/select';
import Button from '@/components/button';
import { GetEventoWithCuestionariosWithCupos } from '@/types/evento';
import { formatearRangoFechas } from '@/utils/date-utils';
interface Props {
evento?: GetEventoWithCuestionariosWithCupos;
formulario: FormularioCreacion;
onChange: (formularioActualizado: FormularioCreacion) => void;
}
@@ -49,7 +52,11 @@ const tiposCuestionario = [
{ label: 'Evaluación', value: 2 },
];
export default function FormularioEditor({ formulario, onChange }: Props) {
export default function FormularioEditor({
formulario,
onChange,
evento,
}: Props) {
function actualizarCampo<K extends keyof FormularioCreacion>(
campo: K,
valor: FormularioCreacion[K]
@@ -173,7 +180,7 @@ export default function FormularioEditor({ formulario, onChange }: Props) {
<div className="col-md-6">
<SimpleInput
label="Fecha de Inicio"
type="date"
type="datetime-local"
value={formulario.fecha_inicio.slice(0, 16)}
onChange={(e) => actualizarCampo('fecha_inicio', e.target.value)}
/>
@@ -181,12 +188,18 @@ export default function FormularioEditor({ formulario, onChange }: Props) {
<div className="col-md-6">
<SimpleInput
label="Fecha de Fin"
type="date"
type="datetime-local"
value={formulario.fecha_fin.slice(0, 16)}
onChange={(e) => actualizarCampo('fecha_fin', e.target.value)}
/>
</div>
</div>
{evento && (
<div className="badge bg-primary fs-6 w-100 mb-3">
Este evento se realizara{' '}
{formatearRangoFechas(evento.fecha_inicio, evento.fecha_fin)}
</div>
)}
<Select
label="Tipo de Cuestionario"
+127 -82
View File
@@ -69,93 +69,138 @@ export default function EditEvento({
};
return (
<div className="row border p-3 rounded my-4">
<h2>Editar Información del Evento</h2>
<p>Modifica los datos del evento que desees actualizar.</p>
<div className="my-2">
<div className="p-4 border rounded bg-light">
<h2 className="mb-2">Editar Información del Evento</h2>
<p className="mb-0 text-muted">
Modifica los datos del evento que desees actualizar y confrimalos.
</p>
</div>
<ImageUploader
label="Banner del evento"
defaultBanner={
evento.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<div className="p-4">
<div className="row">
{/* Columna Izquierda - Banner y Nombre */}
<div className="col-lg-6">
<div className="pe-lg-3">
<ImageUploader
label="Banner del evento"
defaultBanner={
evento.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<p className="text-muted mt-2 small">
<strong>
<i className="bi bi-info-circle-fill me-2"></i>
Esta es la imagen que se verá en el carrusel del evento.
</strong>
<br />
<strong>
<i className="bi bi-image me-2"></i>
Dimensiones recomendadas: 1200x600 píxeles.
</strong>
<br />
<strong>
<i className="bi bi-file-earmark-image me-2"></i>
Formatos soportados: PNG, JPG, JPEG.
</strong>
</p>
</div>
</div>
<Input
label="Nombre del evento"
required
value={evento.nombre_evento}
onChange={(e) => handleChange('nombre_evento', e.target.value)}
/>
{/* Columna Derecha - Descripción, Tipo y Fechas */}
<div className="col-lg-6">
<div className="ps-lg-3">
<h4 className="mb-3">Detalles del Evento</h4>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={evento.descripcion_evento ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange('descripcion_evento', texto);
}
}}
height={250}
commands={[commands.bold, commands.italic, commands.hr]}
/>
<Input
label="Nombre del evento"
required
value={evento.nombre_evento}
onChange={(e) => handleChange('nombre_evento', e.target.value)}
/>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={evento.descripcion_evento ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange('descripcion_evento', texto);
}
}}
height={200}
commands={[commands.bold, commands.italic, commands.hr]}
/>
</div>
</div>
<Select
label="Tipo de evento"
value={evento.tipo_evento}
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={[
{ label: 'Conferencia', value: 'conferencia' },
{ label: 'Taller', value: 'taller' },
{ label: 'Seminario', value: 'seminario' },
{ label: 'Curso', value: 'curso' },
{ label: 'Webinar', value: 'webinar' },
{ label: 'Reunión', value: 'reunion' },
{ label: 'Feria', value: 'feria' },
{ label: 'Concierto', value: 'concierto' },
{ label: 'Exposición', value: 'exposicion' },
{ label: 'Deportivo', value: 'deportivo' },
{ label: 'Cultural', value: 'cultural' },
{ label: 'Otro', value: 'otro' },
]}
/>
<div className="row">
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<Input
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_fin))}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<Select
label="Tipo de evento"
value={evento.tipo_evento}
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={[
{ label: 'Conferencia', value: 'conferencia' },
{ label: 'Taller', value: 'taller' },
{ label: 'Seminario', value: 'seminario' },
{ label: 'Curso', value: 'curso' },
{ label: 'Webinar', value: 'webinar' },
{ label: 'Reunión', value: 'reunion' },
{ label: 'Feria', value: 'feria' },
{ label: 'Concierto', value: 'concierto' },
{ label: 'Exposición', value: 'exposicion' },
{ label: 'Deportivo', value: 'deportivo' },
{ label: 'Cultural', value: 'cultural' },
{ label: 'Otro', value: 'otro' },
]}
/>
<div className="col-md-6">
<Input
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<Input
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(evento.fecha_fin))}
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
/>
</div>
<div>
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
{/* Botón de guardar en la parte inferior */}
<div className="row mt-4">
<div className="col-12">
<div className="d-flex justify-content-end">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
</div>
</div>
</div>
</div>
);
+123 -77
View File
@@ -65,87 +65,133 @@ export default function EditFormulario({
};
return (
<div className="row border p-3 rounded my-4">
<h2>Editar Información del formulario</h2>
<p>Modifica los datos del evento que desees actualizar.</p>
<ImageUploader
label="Banner del formulario"
defaultBanner={
cuestionario.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<div className="col-md-8">
<SimpleInput
label="Nombre del formulario"
required
value={cuestionario.nombre_form}
onChange={(e) => handleChange('nombre_form', e.target.value)}
/>
</div>
<div className="col-md-4">
<SimpleInput
label="Cupo máximo (Si no se requiere, dejar en blanco)"
type="number"
value={cuestionario.cupo_maximo ?? ''}
onChange={(e) =>
handleChange('cupo_maximo', e.target.value ? e.target.value : '')
}
/>
<div className="my-2">
<div className="p-4 border rounded bg-light">
<h2 className="mb-2">Editar Información del Formulario</h2>
<p className="mb-0 text-muted">
Modifica los datos del formulario que desees actualizar y confrimalos.
</p>
</div>
<div className="mb-4">
<label className="form-label">Descripción del evento</label>
<div data-color-mode="light">
<MDEditor
value={cuestionario.descripcion ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange(
'descripcion' as keyof GetCuestionario,
texto
);
}
}}
height={250}
commands={[commands.bold, commands.italic, commands.hr]}
/>
<div className="p-4">
<div className="row">
{/* Columna Izquierda - Banner */}
<div className="col-lg-6">
<div className="pe-lg-3">
<ImageUploader
label="Banner del formulario"
defaultBanner={
cuestionario.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${cuestionario.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
<p className="text-muted mt-2 small">
<strong>
<i className="bi bi-info-circle-fill me-2"></i>
Esta es la imagen que se usara para la carta de presentación
del formulario.
</strong>
<br />
<strong>
<i className="bi bi-image me-2"></i>
Dimensiones recomendadas: 1200x600 píxeles.
</strong>
<br />
<strong>
<i className="bi bi-file-earmark-image me-2"></i>
Formatos soportados: PNG, JPG, JPEG.
</strong>
</p>
</div>
</div>
{/* Columna Derecha - Detalles del formulario */}
<div className="col-lg-6">
<div className="ps-lg-3">
<h4 className="mb-3">Detalles del Formulario</h4>
<SimpleInput
label="Nombre del formulario"
required
value={cuestionario.nombre_form}
onChange={(e) => handleChange('nombre_form', e.target.value)}
/>
<SimpleInput
label="Cupo máximo (Si no se requiere, dejar en blanco)"
type="number"
value={cuestionario.cupo_maximo ?? ''}
onChange={(e) =>
handleChange(
'cupo_maximo',
e.target.value ? e.target.value : ''
)
}
/>
<div className="mb-4">
<label className="form-label">Descripción del formulario</label>
<div data-color-mode="light">
<MDEditor
value={cuestionario.descripcion ?? ''}
onChange={(value) => {
const texto = value || '';
if (texto.length <= 500) {
handleChange(
'descripcion' as keyof GetCuestionario,
texto
);
}
}}
height={200}
commands={[commands.bold, commands.italic, commands.hr]}
/>
</div>
</div>
<div className="row">
<div className="col-md-6">
<SimpleInput
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_fin))}
onChange={(e) =>
handleChange('fecha_fin', new Date(e.target.value))
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de inicio"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_inicio))}
onChange={(e) =>
handleChange('fecha_inicio', new Date(e.target.value))
}
/>
</div>
<div className="col-md-6">
<SimpleInput
label="Fecha de fin"
type="datetime-local"
className={{ container: 'mb-3' }}
value={formatDateLocal(new Date(cuestionario.fecha_fin))}
onChange={(e) => handleChange('fecha_fin', new Date(e.target.value))}
/>
</div>
<div>
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
{/* Botón de guardar en la parte inferior */}
<div className="row mt-4">
<div className="col-12">
<div className="d-flex justify-content-end">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
</div>
</div>
</div>
</div>
);
@@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react';
import MarkdownRenderer from '@/components/markdown-render';
import { useGetApi } from '@/hooks/use-get-api';
import { GetCuestionario } from '@/types/evento';
import SimpleInput from '@/components/input';
@@ -340,13 +339,7 @@ export default function FormularioRegistro({
}
return (
<div className="preguntas">
<hr />
<h2 className="text-xl font-bold">{data?.cuestionario.nombre_form}</h2>
{data?.cuestionario.descripcion && (
<MarkdownRenderer markdown={data.cuestionario.descripcion} />
)}
<div>
{preguntaComunidad && (
<div className="my-4">
<label
+176 -35
View File
@@ -24,45 +24,186 @@ export default function FormularioPage({
return (
<div className="container my-5">
{/* Sección de banners mejorada */}
<div className="text-center mb-4">
{data?.banner && (
<Image
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`}
alt={data?.nombre_evento || ''}
width={800}
height={400}
className="img-fluid rounded"
/>
)}
{data?.banner && data?.cuestionario.banner ? (
/* Ambos banners disponibles */
<div className="d-flex align-items-center justify-content-center gap-3 flex-wrap">
<div className="position-relative">
<Image
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`}
alt={`Banner del evento: ${data.nombre_evento}`}
width={350}
height={200}
className="img-fluid rounded shadow-sm"
style={{ objectFit: 'cover' }}
/>
<div className="position-absolute bottom-0 start-0 m-2">
<span className="badge bg-primary bg-opacity-90">
<i className="bi bi-calendar-event me-1"></i>
Evento
</span>
</div>
</div>
<div className="d-flex align-items-center justify-content-center">
<div
className="rounded-circle bg-light border d-flex align-items-center justify-content-center"
style={{ width: '50px', height: '50px' }}
>
<i className="bi bi-x-lg fs-4 text-muted"></i>
</div>
</div>
<div className="position-relative">
<Image
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.cuestionario.banner}`}
alt={`Banner del formulario: ${data.cuestionario.nombre_form}`}
width={350}
height={200}
className="img-fluid rounded shadow-sm"
style={{ objectFit: 'cover' }}
/>
<div className="position-absolute bottom-0 start-0 m-2">
<span className="badge bg-success bg-opacity-90">
<i className="bi bi-file-earmark-text me-1"></i>
Formulario
</span>
</div>
</div>
</div>
) : data?.banner ? (
/* Solo banner del evento */
<div>
<Image
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.banner}`}
alt={data?.nombre_evento || ''}
width={800}
height={400}
className="img-fluid rounded shadow"
/>
<p className="text-muted mt-2 small">
<i className="bi bi-calendar-event me-1"></i>
Banner del evento
</p>
</div>
) : data?.cuestionario.banner ? (
/* Solo banner del formulario */
<div>
<Image
src={`${process.env.NEXT_PUBLIC_API_URL}/banners/${data.cuestionario.banner}`}
alt={data?.cuestionario.nombre_form || ''}
width={800}
height={400}
className="img-fluid rounded shadow"
/>
<p className="text-muted mt-2 small">
<i className="bi bi-file-earmark-text me-1"></i>
Banner del formulario
</p>
</div>
) : null}
</div>
<h1>{data?.nombre_evento}</h1>
{data?.descripcion_evento && (
<MarkdownRenderer markdown={data?.descripcion_evento} />
)}
{data?.fecha_fin && data?.fecha_inicio && (
<>
<p className="mb-0 mt-2 text-muted">Horario:</p>
<p className="text-muted">
<i className="bi bi-clock me-1 text-primary"></i>
{formatFecha(data.fecha_inicio!, {
horas: true,
minutos: true,
})}{' '}
al{' '}
{formatFecha(data.fecha_fin!, {
horas: true,
minutos: true,
})}
</p>
</>
)}
{/* Layout de dos columnas */}
<div className="row">
{/* Columna izquierda - Información */}
<div className="col-lg-5">
{/* Información del evento */}
<div className="mb-4">
<h1 className="mb-3">{data?.nombre_evento}</h1>
{data?.descripcion_evento && (
<div className="mb-3">
<MarkdownRenderer markdown={data?.descripcion_evento} />
</div>
)}
{data && (
<FormularioRegistro
id_cuestionario={data?.cuestionario.id_cuestionario}
/>
)}
{data?.fecha_fin && data?.fecha_inicio && (
<div className="mb-3">
<p className="mb-1 fw-semibold text-muted">
<i className="bi bi-calendar-event me-2"></i>
Horario del evento:
</p>
<p className="text-muted ps-4">
<i className="bi bi-clock me-1 text-primary"></i>
{formatFecha(data.fecha_inicio!, {
horas: true,
minutos: true,
})}{' '}
al{' '}
{formatFecha(data.fecha_fin!, {
horas: true,
minutos: true,
})}
</p>
</div>
)}
</div>
{/* Información del formulario */}
{data?.cuestionario && (
<div className="card border-0 bg-light">
<div className="card-body">
<h5 className="card-title text-primary mb-3">
<i className="bi bi-file-earmark-text me-2"></i>
Registro al formulario
</h5>
<div className="mb-3">
<h6 className="fw-bold mb-2">Te estás registrando a:</h6>
<p className="mb-1">
<span className="badge bg-primary me-2">Formulario</span>
<strong>{data.cuestionario.nombre_form}</strong>
</p>
<p className="mb-0 text-muted small">
<span className="badge bg-secondary me-2">Evento</span>
{data.nombre_evento}
</p>
</div>
{data.cuestionario.descripcion && (
<div className="mb-3">
<h6 className="fw-bold mb-2">Descripción:</h6>
<MarkdownRenderer
markdown={data.cuestionario.descripcion}
/>
</div>
)}
{data.cuestionario.fecha_inicio &&
data.cuestionario.fecha_fin && (
<div className="mb-0">
<h6 className="fw-bold mb-2">Horario del formulario:</h6>
<p className="text-muted small mb-0">
<i className="bi bi-clock me-1"></i>
{formatFecha(data.cuestionario.fecha_inicio, {
horas: true,
minutos: true,
})}{' '}
al{' '}
{formatFecha(data.cuestionario.fecha_fin, {
horas: true,
minutos: true,
})}
</p>
</div>
)}
</div>
</div>
)}
</div>
{/* Columna derecha - Formulario */}
<div className="col-lg-7">
<div className="ps-lg-4">
{data && (
<FormularioRegistro
id_cuestionario={data?.cuestionario.id_cuestionario}
/>
)}
</div>
</div>
</div>
</div>
);
}