cambios en el formulario osp: rol autoridad, campos responsable cambiados, esquema cambiado, añadida columna fecha de creacion a la tabla

This commit is contained in:
2026-03-04 15:07:31 -04:00
parent f910aea3cc
commit d6de7527e4
13 changed files with 4916 additions and 125 deletions

View File

@@ -21,14 +21,12 @@ export default function EditTrainingPage() {
}
return (
<PageContainer scrollable>
<div className="flex-1 space-y-4">
<CreateTrainingForm
defaultValues={training}
onSuccess={() => router.push('/dashboard/formulario')}
onCancel={() => router.back()}
/>
</div>
</PageContainer>
<div className="p-6 space-y-6">
<CreateTrainingForm
defaultValues={training}
onSuccess={() => router.push('/dashboard/formulario')}
onCancel={() => router.back()}
/>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import PageContainer from '@/components/layout/page-container';
// import PageContainer from '@/components/layout/page-container';
import { TrainingHeader } from '@/feactures/training/components/training-header';
import TrainingList from '@/feactures/training/components/training-list';
import TrainingTableAction from '@/feactures/training/components/training-tables/training-table-action';
@@ -23,17 +23,17 @@ export default async function Page({ searchParams }: PageProps) {
} = searchParamsCache.parse(await searchParams);
return (
<PageContainer>
<div className="flex flex-1 flex-col space-y-6">
<TrainingHeader />
<TrainingTableAction />
<TrainingList
initialPage={page}
initialSearch={searchQuery}
initialLimit={limit || 10}
apiUrl={env.API_URL}
/>
</div>
</PageContainer>
// <PageContainer>
<div className="flex flex-1 flex-col space-y-6 p-6">
<TrainingHeader />
<TrainingTableAction />
<TrainingList
initialPage={page}
initialSearch={searchQuery}
initialLimit={limit || 10}
apiUrl={env.API_URL}
/>
</div>
// </PageContainer>
);
}

View File

@@ -76,11 +76,10 @@ export function CreateTrainingForm({
const [disabledMunicipality, setDisabledMunicipality] = React.useState(true);
const [disabledParish, setDisabledParish] = React.useState(true);
const [coorState, setcoorState] = React.useState(0);
const [coorMunicipality, setcoorMunicipality] = React.useState(0);
const [disabledCoorMunicipality, setDisabledCoorMunicipality] =
React.useState(true);
const [disabledCoorParish, setDisabledCoorParish] = React.useState(true);
// const [coorState, setcoorState] = React.useState(0);
// const [coorMunicipality, setcoorMunicipality] = React.useState(0);
// const [disabledCoorMunicipality, setDisabledCoorMunicipality] = React.useState(true);
// const [disabledCoorParish, setDisabledCoorParish] = React.useState(true);
const [selectedFiles, setSelectedFiles] = React.useState<File[]>([]);
@@ -107,11 +106,11 @@ export function CreateTrainingForm({
coorParish: defaultValues?.coorParish || undefined,
coorPhone: defaultValues?.coorPhone || '',
visitDate: formatToLocalISO(defaultValues?.visitDate),
productiveActivity: defaultValues?.productiveActivity || '',
productiveActivity: defaultValues?.productiveActivity || undefined,
ecoSector: defaultValues?.ecoSector || undefined,
productiveSector: defaultValues?.productiveSector || undefined,
centralProductiveActivity: defaultValues?.centralProductiveActivity || '',
mainProductiveActivity: defaultValues?.mainProductiveActivity || '',
centralProductiveActivity: defaultValues?.centralProductiveActivity || undefined,
mainProductiveActivity: defaultValues?.mainProductiveActivity || undefined,
photo1: defaultValues?.photo1 || '',
photo2: defaultValues?.photo2 || '',
@@ -149,9 +148,9 @@ export function CreateTrainingForm({
generalObservations: defaultValues?.generalObservations || '',
ospResponsibleEmail: defaultValues?.ospResponsibleEmail || '',
paralysisReason: defaultValues?.paralysisReason || '',
infrastructureMt2: defaultValues?.infrastructureMt2 || '',
infrastructureMt2: defaultValues?.infrastructureMt2 || undefined,
hasTransport: defaultValues?.hasTransport || false,
structureType: defaultValues?.structureType || '',
structureType: defaultValues?.structureType || undefined,
isOpenSpace: defaultValues?.isOpenSpace || false,
equipmentList: defaultValues?.equipmentList || [],
productionList: defaultValues?.productionList || [],
@@ -249,14 +248,14 @@ export function CreateTrainingForm({
setDisabledParish(false);
}
if (defaultValues.coorState) {
setcoorState(Number(defaultValues.coorState));
setDisabledCoorMunicipality(false);
}
if (defaultValues.coorMunicipality) {
setcoorMunicipality(Number(defaultValues.coorMunicipality));
setDisabledCoorParish(false);
}
// if (defaultValues.coorState) {
// setcoorState(Number(defaultValues.coorState));
// setDisabledCoorMunicipality(false);
// }
// if (defaultValues.coorMunicipality) {
// setcoorMunicipality(Number(defaultValues.coorMunicipality));
// setDisabledCoorParish(false);
// }
}
}, [defaultValues]);
@@ -489,13 +488,11 @@ export function CreateTrainingForm({
2. Datos de la Organización Socioproductiva (OSP)
</CardTitle>
</CardHeader>
{/* CAMBIO 1: lg:grid-cols-2 para evitar columnas estrechas en tablets */}
<CardContent className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
<FormField
control={form.control}
name="ospType"
render={({ field }) => (
/* CAMBIO 2: flex flex-col y space-y-3 para forzar separación vertical interna */
<FormItem className="w-full flex flex-col space-y-3">
<FormLabel className="whitespace-normal leading-tight font-semibold">
Tipo de Organización
@@ -1103,7 +1100,7 @@ export function CreateTrainingForm({
name="womenCount"
render={({ field }) => (
<FormItem>
<FormLabel>Mujeres (cantidad)</FormLabel>
<FormLabel>Mujeres (Cantidad opcional)</FormLabel>
<FormControl>
<Input type="number" {...field} />
</FormControl>
@@ -1116,7 +1113,7 @@ export function CreateTrainingForm({
name="menCount"
render={({ field }) => (
<FormItem>
<FormLabel>Hombres (cantidad)</FormLabel>
<FormLabel>Hombres (Cantidad opcional)</FormLabel>
<FormControl>
<Input type="number" {...field} />
</FormControl>
@@ -1155,7 +1152,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="col-span-1 lg:col-span-2 flex flex-col space-y-2">
<FormLabel>
Coordenadas de la Ubicación (Google Maps)
Coordenadas de la Ubicación (Google Maps. Opcional)
</FormLabel>
<FormControl>
<Input
@@ -1211,7 +1208,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Rif de la Comuna
Rif de la Comuna (opcional)
</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} />
@@ -1227,7 +1224,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Nombre del Vocero o Vocera
Nombre del Vocero o Vocera (opcional)
</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} />
@@ -1243,7 +1240,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Número de Teléfono del Vocero
Número de Teléfono del Vocero (opcional)
</FormLabel>
<FormControl>
<Input
@@ -1325,7 +1322,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Rif del Consejo Comunal
Rif del Consejo Comunal (opcional)
</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} />
@@ -1341,7 +1338,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Nombre del Vocero o Vocera
Nombre del Vocero o Vocera (opcional)
</FormLabel>
<FormControl>
<Input {...field} value={field.value ?? ''} />
@@ -1357,7 +1354,7 @@ export function CreateTrainingForm({
render={({ field }) => (
<FormItem className="w-full flex flex-col space-y-2">
<FormLabel className="font-semibold">
Número de Teléfono del Vocero
Número de Teléfono del Vocero (opcional)
</FormLabel>
<FormControl>
<Input
@@ -1419,9 +1416,31 @@ export function CreateTrainingForm({
<FormField
control={form.control}
name="ospResponsibleFullname"
name="ospResponsiblePhone"
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono</FormLabel>
<FormControl>
<Input
{...field}
value={field.value ?? ''}
placeholder="Ej. 04121234567"
onChange={(e) => {
const val = e.target.value.replace(/\D/g, '');
field.onChange(val.slice(0, 11));
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="ospResponsibleFullname"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Nombre y apellido</FormLabel>
<FormControl>
<Input {...field} />
@@ -1431,7 +1450,7 @@ export function CreateTrainingForm({
)}
/>
<FormField
{/* <FormField
control={form.control}
name="ospResponsibleRif"
render={({ field }) => (
@@ -1443,9 +1462,9 @@ export function CreateTrainingForm({
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
{/* <FormField
control={form.control}
name="civilState"
render={({ field }) => (
@@ -1471,31 +1490,9 @@ export function CreateTrainingForm({
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
control={form.control}
name="ospResponsiblePhone"
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono</FormLabel>
<FormControl>
<Input
{...field}
value={field.value ?? ''}
placeholder="Ej. 04121234567"
onChange={(e) => {
const val = e.target.value.replace(/\D/g, '');
field.onChange(val.slice(0, 11));
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
{/* <FormField
control={form.control}
name="ospResponsibleEmail"
render={({ field }) => (
@@ -1511,9 +1508,9 @@ export function CreateTrainingForm({
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
{/* <FormField
control={form.control}
name="familyBurden"
render={({ field }) => (
@@ -1529,9 +1526,9 @@ export function CreateTrainingForm({
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
{/* <FormField
control={form.control}
name="numberOfChildren"
render={({ field }) => (
@@ -1547,7 +1544,7 @@ export function CreateTrainingForm({
<FormMessage />
</FormItem>
)}
/>
/> */}
</CardContent>
</Card>

View File

@@ -22,6 +22,15 @@ export function columns({ apiUrl }: ColumnsProps): ColumnDef<TrainingSchema>[] {
accessorKey: 'ospType',
header: 'Tipo',
},
{
accessorKey: 'created_at',
header: 'Fecha de creación',
cell: ({ row }) => {
// console.log(row.getValue('created_at'));
const date = row.getValue('created_at') as string;
return date ? new Date(date).toLocaleString() : 'N/A';
},
},
{
accessorKey: 'currentStatus',
header: 'Estatus',

View File

@@ -22,17 +22,17 @@ export default function TrainingTableAction() {
setPage={setPage}
/>
</div>{' '}
{['superadmin', 'admin', 'manager', 'coordinators'].includes(
{['superadmin', 'autoridad', 'admin', 'manager', 'coordinators'].includes(
role ?? '',
) && (
<Button
onClick={() => router.push(`/dashboard/formulario/nuevo`)}
size="sm"
>
<Plus className="h-4 w-4" />
<span className="hidden md:inline">Nuevo Registro</span>
</Button>
)}
<Button
onClick={() => router.push(`/dashboard/formulario/nuevo`)}
size="sm"
>
<Plus className="h-4 w-4" />
<span className="hidden md:inline">Nuevo Registro</span>
</Button>
)}
</div>
);
}

View File

@@ -356,10 +356,10 @@ export function TrainingViewModal({
))}
{(!data.equipmentList ||
data.equipmentList.length === 0) && (
<p className="text-sm text-muted-foreground italic">
No hay equipamiento registrado.
</p>
)}
<p className="text-sm text-muted-foreground italic">
No hay equipamiento registrado.
</p>
)}
</CardContent>
</Card>
@@ -389,10 +389,10 @@ export function TrainingViewModal({
))}
{(!data.productionList ||
data.productionList.length === 0) && (
<p className="text-sm text-muted-foreground italic">
No hay materia prima registrada.
</p>
)}
<p className="text-sm text-muted-foreground italic">
No hay materia prima registrada.
</p>
)}
</CardContent>
</Card>
</div>
@@ -438,12 +438,12 @@ export function TrainingViewModal({
label="Teléfono"
value={data.ospResponsiblePhone}
/>
<DetailItem label="Email" value={data.ospResponsibleEmail} />
{/* <DetailItem label="Email" value={data.ospResponsibleEmail} />
<DetailItem
label="Carga Familiar"
value={data.familyBurden}
/>
<DetailItem label="Hijos" value={data.numberOfChildren} />
<DetailItem label="Hijos" value={data.numberOfChildren} /> */}
</Section>
</div>

View File

@@ -28,7 +28,7 @@ export const ACTIVIDAD_PRINCIPAL = {
PATIOS_PRODUCTIVOS: 'PATIOS PRODUCTIVOS O CONUCOS',
TRANSFORMACION_MATERIA: 'TRANSFORMACION DE LA MATERIA PRIMA',
TEXTIL: 'TALLER DE COFECCION TEXTIL',
CONSTRUCCION: 'CONSTRUCION',
CONSTRUCCION: 'CONSTRUCCION',
BIENES_SERVICIOS: 'OFRECER PRODUCTOS DE BIENES Y SERVICIOS',
VISITAS_GUIADAS: 'VISITAS GUIADAS',
ALOJAMIENTO: 'ALOJAMIENTO',

View File

@@ -28,9 +28,7 @@ export const trainingSchema = z.object({
.min(1, { message: 'Nombre del coordinador es requerido' }),
coorPhone: z
.string()
.optional()
.nullable()
.refine((val) => !val || /^(04|02)\d{9}$/.test(val), {
.refine((val) => /^(04|02)\d{9}$/.test(val), {
message: 'El teléfono debe tener 11 dígitos y comenzar con 04 o 02',
}),
visitDate: z
@@ -39,13 +37,11 @@ export const trainingSchema = z.object({
//Datos de la organización socioproductiva (OSP)
ospType: z.string().min(1, { message: 'Tipo de OSP es requerido' }),
ecoSector: z.string().optional().or(z.literal('')).nullable(),
productiveSector: z.string().optional().or(z.literal('')).nullable(),
centralProductiveActivity: z.string().optional().or(z.literal('')).nullable(),
mainProductiveActivity: z.string().optional().or(z.literal('')).nullable(),
productiveActivity: z
.string()
.min(1, { message: 'Actividad productiva es requerida' }),
ecoSector: z.string({ message: 'Sector Económico es requerido' }),
productiveSector: z.string({ message: 'Sector Productivo es requerido' }),
centralProductiveActivity: z.string({ message: 'Actividad Central Productiva es requerido' }),
mainProductiveActivity: z.string({ message: 'Actividad Productiva Principal es requerida' }),
productiveActivity: z.string({ message: 'Actividad Productiva es requerida' }),
ospRif: z.string().optional().or(z.literal('')).nullable(),
ospName: z.string().optional().or(z.literal('')).nullable(),
companyConstitutionYear: z.coerce
@@ -56,7 +52,7 @@ export const trainingSchema = z.object({
.string()
.min(1, { message: 'Estatus actual es requerido' })
.default('ACTIVA'),
infrastructureMt2: z.string().optional().or(z.literal('')).nullable(),
infrastructureMt2: z.string({ message: 'Infraestructura es requerida' }),
hasTransport: z
.preprocess(
(val) => val === 'true' || val === true || val === 1 || val === '1',
@@ -65,7 +61,7 @@ export const trainingSchema = z.object({
.optional()
.nullable()
.default(false),
structureType: z.string().optional().or(z.literal('')).nullable(),
structureType: z.string({ message: 'Tipo de estructura es requerido' }),
isOpenSpace: z
.preprocess(
(val) => val === 'true' || val === true || val === 1 || val === '1',
@@ -115,8 +111,8 @@ export const trainingSchema = z.object({
.string()
.min(1, { message: 'Dirección de la OSP es requerida' }),
ospGoogleMapsLink: z.string().optional().or(z.literal('')).nullable(),
communeName: z.string().optional().or(z.literal('')).nullable(),
siturCodeCommune: z.string().optional().or(z.literal('')).nullable(),
communeName: z.string().min(1, { message: 'Nombre de la comuna es requerida' }),
siturCodeCommune: z.string().min(1, { message: 'Código SITUR de la comuna es requerida' }),
communeRif: z.string().optional().or(z.literal('')).nullable(),
communeSpokespersonName: z.string().optional().or(z.literal('')).nullable(),
communeSpokespersonPhone: z
@@ -135,7 +131,7 @@ export const trainingSchema = z.object({
communalCouncil: z
.string()
.min(1, { message: 'Consejo Comunal es requerido' }),
siturCodeCommunalCouncil: z.string().optional().or(z.literal('')).nullable(),
siturCodeCommunalCouncil: z.string().min(1, { message: 'Código SITUR del Consejo Comunal es requerido' }),
communalCouncilRif: z.string().optional().or(z.literal('')).nullable(),
communalCouncilSpokespersonName: z
.string()
@@ -188,9 +184,9 @@ export const trainingSchema = z.object({
files: z.any().optional(),
//no se envia la backend al crear ni editar el formulario
state: z.number().optional().nullable(),
municipality: z.number().optional().nullable(),
parish: z.number().optional().nullable(),
state: z.number({ message: 'El estado es requerido' }).nullable(),
municipality: z.number({ message: 'Municipio es requerido' }).nullable(),
parish: z.number({ message: 'Parroquia es requerido' }).nullable(),
coorState: z.number().optional().nullable(),
coorMunicipality: z.number().optional().nullable(),
coorParish: z.number().optional().nullable(),
@@ -199,15 +195,117 @@ export const trainingSchema = z.object({
photo3: z.string().optional().nullable(),
createdBy: z.number().optional().nullable(),
updatedBy: z.number().optional().nullable(),
createdAt: z.string().optional().nullable(),
updatedAt: z.string().optional().nullable(),
created_at: z.string().optional().nullable(),
updated_at: z.string().optional().nullable(),
});
export type TrainingSchema = z.infer<typeof trainingSchema>;
export const getTrainingSchema = z.object({
//Datos de la visita
id: z.number().optional(),
coorFullName: z.string(),
coorPhone: z.string(),
visitDate: z.string(),
//Datos de la organización socioproductiva (OSP)
ospType: z.string(),
ecoSector: z.string(),
productiveSector: z.string(),
centralProductiveActivity: z.string(),
mainProductiveActivity: z.string(),
productiveActivity: z.string(),
ospRif: z.string().optional().or(z.literal('')).nullable(),
ospName: z.string().optional().or(z.literal('')).nullable(),
companyConstitutionYear: z.coerce.number(),
currentStatus: z.string(),
infrastructureMt2: z.string(),
hasTransport: z
.preprocess(
(val) => val === 'true' || val === true || val === 1 || val === '1',
z.boolean(),
)
.optional()
.nullable()
.default(false),
structureType: z.string(),
isOpenSpace: z
.preprocess(
(val) => val === 'true' || val === true || val === 1 || val === '1',
z.boolean(),
)
.optional()
.nullable()
.default(false),
paralysisReason: z.string().optional().nullable(),
//Datos del Equipamiento
equipmentList: z.array(equipmentItemSchema).optional().default([]),
//Datos de Producción
productionList: z.array(productionItemSchema).optional().default([]),
// Datos de Actividad Productiva
productList: z.array(productItemSchema).optional().default([]),
// Distribución y Exportación
internalDistributionZone: z.string(),
isExporting: z
.preprocess(
(val) => val === 'true' || val === true || val === 1 || val === '1',
z.boolean(),
)
.optional()
.default(false),
externalCountry: z.string().optional().nullable(),
externalCity: z.string().optional().nullable(),
externalDescription: z.string().optional().nullable(),
externalQuantity: z.coerce.string().or(z.number()).optional().nullable(),
externalUnit: z.string().optional().nullable(),
// Mano de obra
womenCount: z.coerce.number(),
menCount: z.coerce.number(),
//Detalles de la ubicación
ospAddress: z.string(),
ospGoogleMapsLink: z.string().optional().or(z.literal('')).nullable(),
communeName: z.string(),
siturCodeCommune: z.string(),
communeRif: z.string().optional().or(z.literal('')).nullable(),
communeSpokespersonName: z.string().optional().or(z.literal('')).nullable(),
communeSpokespersonPhone: z.string(),
communeEmail: z.string(),
communalCouncil: z.string(),
siturCodeCommunalCouncil: z.string(),
communalCouncilRif: z.string().optional(),
communalCouncilSpokespersonName: z.string(),
communalCouncilSpokespersonPhone: z.string(),
communalCouncilEmail: z.string(),
//Datos del Responsable OSP
ospResponsibleCedula: z.string(),
ospResponsibleFullname: z.string(),
ospResponsibleRif: z.string().optional().nullable(),
civilState: z.string().optional().nullable(),
ospResponsiblePhone: z.string(),
ospResponsibleEmail: z.string(),
familyBurden: z.coerce.number().optional(),
numberOfChildren: z.coerce.number().optional(),
//Datos adicionales
generalObservations: z.string().optional().nullable(),
//no se envia la backend al crear ni editar el formulario
state: z.number().nullable(),
municipality: z.number().nullable(),
parish: z.number().nullable(),
coorState: z.number().optional().nullable(),
coorMunicipality: z.number().optional().nullable(),
coorParish: z.number().optional().nullable(),
photo1: z.string().optional().nullable(),
photo2: z.string().optional().nullable(),
photo3: z.string().optional().nullable(),
createdBy: z.number().optional().nullable(),
updatedBy: z.number().optional().nullable(),
created_at: z.string().optional().nullable(),
updated_at: z.string().optional().nullable(),
});
export const trainingApiResponseSchema = z.object({
message: z.string(),
data: z.array(trainingSchema),
data: z.array(getTrainingSchema),
meta: z.object({
page: z.number(),
limit: z.number(),