diff --git a/apps/api/.env.example b/apps/api/.env.example index 8b043fc..b643e4f 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -17,3 +17,10 @@ DATABASE_URL="postgresql://postgres:local**@localhost:5432/caja_ahorro" #url con MAIL_HOST=gmail MAIL_USERNAME= MAIL_PASSWORD= + +MINIO_ENDPOINT= +MINIO_PORT= +MINIO_ACCESS_KEY= +MINIO_SECRET_KEY= +MINIO_BUCKET= +MINIO_USE_SSL= diff --git a/apps/api/package.json b/apps/api/package.json index c32d8ff..8cf55f7 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -44,6 +44,7 @@ "drizzle-orm": "0.40.0", "express": "5.1.0", "joi": "17.13.3", + "minio": "^8.0.6", "moment": "2.30.1", "path-to-regexp": "8.2.0", "pg": "8.13.3", diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 5914eb0..9001686 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -10,16 +10,17 @@ import { ConfigModule } from '@nestjs/config'; import { APP_GUARD } from '@nestjs/core'; import { JwtModule } from '@nestjs/jwt'; import { ThrottlerGuard } from '@nestjs/throttler'; +import { MinioModule } from './common/minio/minio.module'; import { DrizzleModule } from './database/drizzle.module'; import { AuthModule } from './features/auth/auth.module'; import { ConfigurationsModule } from './features/configurations/configurations.module'; -import { LocationModule } from './features/location/location.module' +import { InventoryModule } from './features/inventory/inventory.module'; +import { LocationModule } from './features/location/location.module'; import { MailModule } from './features/mail/mail.module'; import { RolesModule } from './features/roles/roles.module'; -import { UserRolesModule } from './features/user-roles/user-roles.module'; import { SurveysModule } from './features/surveys/surveys.module'; -import { InventoryModule } from './features/inventory/inventory.module'; import { TrainingModule } from './features/training/training.module'; +import { UserRolesModule } from './features/user-roles/user-roles.module'; @Module({ providers: [ @@ -51,6 +52,7 @@ import { TrainingModule } from './features/training/training.module'; NodeMailerModule, LoggerModule, ThrottleModule, + MinioModule, UsersModule, AuthModule, MailModule, @@ -61,7 +63,7 @@ import { TrainingModule } from './features/training/training.module'; SurveysModule, LocationModule, InventoryModule, - TrainingModule + TrainingModule, ], }) -export class AppModule { } +export class AppModule {} diff --git a/apps/api/src/common/config/envs.ts b/apps/api/src/common/config/envs.ts index 185c57f..82fad3a 100644 --- a/apps/api/src/common/config/envs.ts +++ b/apps/api/src/common/config/envs.ts @@ -14,6 +14,12 @@ interface EnvVars { MAIL_HOST: string; MAIL_USERNAME: string; MAIL_PASSWORD: string; + MINIO_ENDPOINT: string; + MINIO_PORT: number; + MINIO_ACCESS_KEY: string; + MINIO_SECRET_KEY: string; + MINIO_BUCKET: string; + MINIO_USE_SSL: boolean; } const envsSchema = joi @@ -30,6 +36,12 @@ const envsSchema = joi MAIL_HOST: joi.string(), MAIL_USERNAME: joi.string(), MAIL_PASSWORD: joi.string(), + MINIO_ENDPOINT: joi.string().required(), + MINIO_PORT: joi.number().required(), + MINIO_ACCESS_KEY: joi.string().required(), + MINIO_SECRET_KEY: joi.string().required(), + MINIO_BUCKET: joi.string().required(), + MINIO_USE_SSL: joi.boolean().default(false), }) .unknown(true); @@ -54,4 +66,10 @@ export const envs = { mail_host: envVars.MAIL_HOST, mail_username: envVars.MAIL_USERNAME, mail_password: envVars.MAIL_PASSWORD, + minio_endpoint: envVars.MINIO_ENDPOINT, + minio_port: envVars.MINIO_PORT, + minio_access_key: envVars.MINIO_ACCESS_KEY, + minio_secret_key: envVars.MINIO_SECRET_KEY, + minio_bucket: envVars.MINIO_BUCKET, + minio_use_ssl: envVars.MINIO_USE_SSL, }; diff --git a/apps/api/src/common/minio/minio.module.ts b/apps/api/src/common/minio/minio.module.ts new file mode 100644 index 0000000..50ba504 --- /dev/null +++ b/apps/api/src/common/minio/minio.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { MinioService } from './minio.service'; + +@Global() +@Module({ + providers: [MinioService], + exports: [MinioService], +}) +export class MinioModule {} diff --git a/apps/api/src/common/minio/minio.service.ts b/apps/api/src/common/minio/minio.service.ts new file mode 100644 index 0000000..8204c02 --- /dev/null +++ b/apps/api/src/common/minio/minio.service.ts @@ -0,0 +1,118 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import * as Minio from 'minio'; +import { envs } from '../config/envs'; + +@Injectable() +export class MinioService implements OnModuleInit { + private readonly minioClient: Minio.Client; + private readonly logger = new Logger(MinioService.name); + private readonly bucketName = envs.minio_bucket; + + constructor() { + this.minioClient = new Minio.Client({ + endPoint: envs.minio_endpoint, + port: envs.minio_port, + useSSL: envs.minio_use_ssl, + accessKey: envs.minio_access_key, + secretKey: envs.minio_secret_key, + }); + } + + async onModuleInit() { + await this.ensureBucketExists(); + } + + private async ensureBucketExists() { + // Ejecuta esto siempre al menos una vez para asegurar que sea público + const policy = { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { AWS: ['*'] }, + Action: ['s3:GetObject'], + Resource: [`arn:aws:s3:::${this.bucketName}/*`], + }, + ], + }; + try { + // const bucketExists = await this.minioClient.bucketExists(this.bucketName); + // if (!bucketExists) { + // await this.minioClient.makeBucket(this.bucketName); + // } + + await this.minioClient.setBucketPolicy( + this.bucketName, + JSON.stringify(policy), + ); + this.logger.log(`Public policy ensured for bucket "${this.bucketName}"`); + } catch (error: any) { + this.logger.error(`Error checking/creating bucket: ${error.message}`); + } + } + + async upload( + file: Express.Multer.File, + folder: string = 'general', + ): Promise { + const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname.replace(/\s/g, '_')}`; + const objectName = `${folder}/${fileName}`; + + try { + await this.minioClient.putObject( + this.bucketName, + objectName, + file.buffer, + file.size, + { + 'Content-Type': file.mimetype, + }, + ); + + // Return the URL or the object path. + // Usually, we store the object path and generate a signed URL or use a proxy. + // The user asked for the URL to be stored in the database. + return objectName; + } catch (error: any) { + this.logger.error(`Error uploading file: ${error.message}`); + throw error; + } + } + + async getFileUrl(objectName: string): Promise { + try { + // If the bucket is public, we can just return the URL. + // If private, we need a signed URL. + // For simplicity and common use cases in these projects, I'll generate a signed URL with a long expiration + // or assume there is some way to access it. + // But let's use signed URL for 1 week (maximum is 7 days) if needed, + // or just return the object name if the backend handles the serving. + // The user wants the URL stored in the DB. + + return await this.minioClient.presignedUrl( + 'GET', + this.bucketName, + objectName, + 604800, + ); + } catch (error: any) { + this.logger.error(`Error getting file URL: ${error.message}`); + throw error; + } + } + + getPublicUrl(objectName: string): string { + const protocol = envs.minio_use_ssl ? 'https' : 'http'; + return `${protocol}://${envs.minio_endpoint}:${envs.minio_port}/${this.bucketName}/${objectName}`; + } + + async delete(objectName: string): Promise { + try { + await this.minioClient.removeObject(this.bucketName, objectName); + this.logger.log(`Object "${objectName}" deleted successfully.`); + } catch (error: any) { + this.logger.error(`Error deleting file: ${error.message}`); + // We don't necessarily want to throw if the file is already gone + } + } +} diff --git a/apps/api/src/database/migrations/0020_certain_bushwacker.sql b/apps/api/src/database/migrations/0020_certain_bushwacker.sql new file mode 100644 index 0000000..9f1ccc3 --- /dev/null +++ b/apps/api/src/database/migrations/0020_certain_bushwacker.sql @@ -0,0 +1,5 @@ +ALTER TABLE "training_surveys" ALTER COLUMN "osp_responsible_rif" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "training_surveys" ALTER COLUMN "civil_state" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "training_surveys" ALTER COLUMN "osp_responsible_email" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "training_surveys" ALTER COLUMN "family_burden" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "training_surveys" ALTER COLUMN "number_of_children" DROP NOT NULL; \ No newline at end of file diff --git a/apps/api/src/database/migrations/0021_warm_machine_man.sql b/apps/api/src/database/migrations/0021_warm_machine_man.sql new file mode 100644 index 0000000..73f807b --- /dev/null +++ b/apps/api/src/database/migrations/0021_warm_machine_man.sql @@ -0,0 +1,2 @@ +ALTER TABLE "training_surveys" ALTER COLUMN "osp_rif" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "training_surveys" ALTER COLUMN "osp_name" DROP NOT NULL; \ No newline at end of file diff --git a/apps/api/src/database/migrations/meta/0020_snapshot.json b/apps/api/src/database/migrations/meta/0020_snapshot.json new file mode 100644 index 0000000..fe4dfc1 --- /dev/null +++ b/apps/api/src/database/migrations/meta/0020_snapshot.json @@ -0,0 +1,2041 @@ +{ + "id": "1236a02f-0d6f-4381-9d3a-c79f023908b7", + "prevId": "5f429376-b40b-495e-9f76-4c080cba6824", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_logs": { + "name": "activity_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "activityLogs_idx": { + "name": "activityLogs_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_logs_user_id_users_id_fk": { + "name": "activity_logs_user_id_users_id_fk", + "tableFrom": "activity_logs", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.roles": { + "name": "roles", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "roles_idx": { + "name": "roles_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_session_token": { + "name": "previous_session_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_idx": { + "name": "sessions_idx", + "columns": [ + { + "expression": "session_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "municipality": { + "name": "municipality", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parish": { + "name": "parish", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_two_factor_enabled": { + "name": "is_two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_idx": { + "name": "users_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_state_states_id_fk": { + "name": "users_state_states_id_fk", + "tableFrom": "users", + "tableTo": "states", + "columnsFrom": [ + "state" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_municipality_municipalities_id_fk": { + "name": "users_municipality_municipalities_id_fk", + "tableFrom": "users", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_parish_parishes_id_fk": { + "name": "users_parish_parishes_id_fk", + "tableFrom": "users", + "tableTo": "parishes", + "columnsFrom": [ + "parish" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.user_role": { + "name": "user_role", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_role_idx": { + "name": "user_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_role_user_id_users_id_fk": { + "name": "user_role_user_id_users_id_fk", + "tableFrom": "user_role", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_roles_id_fk": { + "name": "user_role_role_id_roles_id_fk", + "tableFrom": "user_role", + "tableTo": "roles", + "schemaTo": "auth", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verificationToken": { + "name": "verificationToken", + "schema": "auth", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.category_type": { + "name": "category_type", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group": { + "name": "group", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "category_typeIx0": { + "name": "category_typeIx0", + "columns": [ + { + "expression": "group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "category_typeIx1": { + "name": "category_typeIx1", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.localities": { + "name": "localities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parish_id": { + "name": "parish_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "localities_index_03": { + "name": "localities_index_03", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_00": { + "name": "localities_index_00", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_01": { + "name": "localities_index_01", + "columns": [ + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_02": { + "name": "localities_index_02", + "columns": [ + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "localities_state_id_states_id_fk": { + "name": "localities_state_id_states_id_fk", + "tableFrom": "localities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_municipality_id_municipalities_id_fk": { + "name": "localities_municipality_id_municipalities_id_fk", + "tableFrom": "localities", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_parish_id_parishes_id_fk": { + "name": "localities_parish_id_parishes_id_fk", + "tableFrom": "localities", + "tableTo": "parishes", + "columnsFrom": [ + "parish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "localities_name_unique": { + "name": "localities_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.municipalities": { + "name": "municipalities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "municipalities_index_00": { + "name": "municipalities_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "municipalities_state_id_states_id_fk": { + "name": "municipalities_state_id_states_id_fk", + "tableFrom": "municipalities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.parishes": { + "name": "parishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "parishes_index_00": { + "name": "parishes_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "parishes_municipality_id_municipalities_id_fk": { + "name": "parishes_municipality_id_municipalities_id_fk", + "tableFrom": "parishes", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.states": { + "name": "states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "states_index_00": { + "name": "states_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url_img": { + "name": "url_img", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gallery": { + "name": "gallery", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'BORRADOR'" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_user_id_users_id_fk": { + "name": "products_user_id_users_id_fk", + "tableFrom": "products", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.answers_surveys": { + "name": "answers_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "answers": { + "name": "answers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "answers_index_00": { + "name": "answers_index_00", + "columns": [ + { + "expression": "answers", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_01": { + "name": "answers_index_01", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_02": { + "name": "answers_index_02", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "answers_surveys_survey_id_surveys_id_fk": { + "name": "answers_surveys_survey_id_surveys_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "answers_surveys_user_id_users_id_fk": { + "name": "answers_surveys_user_id_users_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.surveys": { + "name": "surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_audience": { + "name": "target_audience", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "questions": { + "name": "questions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "surveys_index_00": { + "name": "surveys_index_00", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.training_surveys": { + "name": "training_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastname": { + "name": "lastname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visit_date": { + "name": "visit_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "coor_phone": { + "name": "coor_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "municipality": { + "name": "municipality", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parish": { + "name": "parish", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "osp_type": { + "name": "osp_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "eco_sector": { + "name": "eco_sector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "productive_sector": { + "name": "productive_sector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "central_productive_activity": { + "name": "central_productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "main_productive_activity": { + "name": "main_productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "productive_activity": { + "name": "productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_rif": { + "name": "osp_rif", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_name": { + "name": "osp_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_constitution_year": { + "name": "company_constitution_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_status": { + "name": "current_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVA'" + }, + "infrastructure_mt2": { + "name": "infrastructure_mt2", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "has_transport": { + "name": "has_transport", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "structure_type": { + "name": "structure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_open_space": { + "name": "is_open_space", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "paralysis_reason": { + "name": "paralysis_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "equipment_list": { + "name": "equipment_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "production_list": { + "name": "production_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "product_list": { + "name": "product_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "osp_address": { + "name": "osp_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_google_maps_link": { + "name": "osp_google_maps_link", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_name": { + "name": "commune_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "situr_code_commune": { + "name": "situr_code_commune", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "commune_rif": { + "name": "commune_rif", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_spokesperson_name": { + "name": "commune_spokesperson_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_spokesperson_cedula": { + "name": "commune_spokesperson_cedula", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commune_spokesperson_rif": { + "name": "commune_spokesperson_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commune_spokesperson_phone": { + "name": "commune_spokesperson_phone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_email": { + "name": "commune_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council": { + "name": "communal_council", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "situr_code_communal_council": { + "name": "situr_code_communal_council", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "communal_council_rif": { + "name": "communal_council_rif", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_spokesperson_name": { + "name": "communal_council_spokesperson_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_spokesperson_cedula": { + "name": "communal_council_spokesperson_cedula", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council_spokesperson_rif": { + "name": "communal_council_spokesperson_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council_spokesperson_phone": { + "name": "communal_council_spokesperson_phone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_email": { + "name": "communal_council_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "osp_responsible_fullname": { + "name": "osp_responsible_fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_cedula": { + "name": "osp_responsible_cedula", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_rif": { + "name": "osp_responsible_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "civil_state": { + "name": "civil_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "osp_responsible_phone": { + "name": "osp_responsible_phone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_email": { + "name": "osp_responsible_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_burden": { + "name": "family_burden", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "number_of_children": { + "name": "number_of_children", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "general_observations": { + "name": "general_observations", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo1": { + "name": "photo1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo2": { + "name": "photo2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo3": { + "name": "photo3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "training_surveys_index_00": { + "name": "training_surveys_index_00", + "columns": [ + { + "expression": "firstname", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "training_surveys_state_states_id_fk": { + "name": "training_surveys_state_states_id_fk", + "tableFrom": "training_surveys", + "tableTo": "states", + "columnsFrom": [ + "state" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_municipality_municipalities_id_fk": { + "name": "training_surveys_municipality_municipalities_id_fk", + "tableFrom": "training_surveys", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_parish_parishes_id_fk": { + "name": "training_surveys_parish_parishes_id_fk", + "tableFrom": "training_surveys", + "tableTo": "parishes", + "columnsFrom": [ + "parish" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_created_by_users_id_fk": { + "name": "training_surveys_created_by_users_id_fk", + "tableFrom": "training_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "training_surveys_updated_by_users_id_fk": { + "name": "training_surveys_updated_by_users_id_fk", + "tableFrom": "training_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "auth.gender": { + "name": "gender", + "schema": "auth", + "values": [ + "FEMENINO", + "MASCULINO" + ] + }, + "public.nationality": { + "name": "nationality", + "schema": "public", + "values": [ + "VENEZOLANO", + "EXTRANJERO" + ] + }, + "auth.status": { + "name": "status", + "schema": "auth", + "values": [ + "ACTIVE", + "INACTIVE" + ] + } + }, + "schemas": { + "auth": "auth" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "auth.user_access_view": { + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n u.id AS user_id,\n u.username,\n u.email,\n u.fullname,\n r.id AS role_id,\n r.name AS role_name\nFROM\n auth.users u\nLEFT JOIN\n auth.user_role ur ON u.id = ur.user_id \nLEFT JOIN\n auth.roles r ON ur.role_id = r.id", + "name": "user_access_view", + "schema": "auth", + "isExisting": false, + "materialized": false + }, + "public.v_product_store": { + "columns": { + "product_id": { + "name": "product_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url_img": { + "name": "url_img", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gallery": { + "name": "gallery", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.gallery, p.address, p.status, p.user_id, u.fullname, u.email, u.phone\n from products p\n left join auth.users as u on u.id = p.user_id", + "name": "v_product_store", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.v_surveys": { + "columns": { + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "target_audience": { + "name": "target_audience", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "definition": "select id as survey_id, title, description, created_at, closing_date, target_audience from surveys\nwhere published = true", + "name": "v_surveys", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/src/database/migrations/meta/0021_snapshot.json b/apps/api/src/database/migrations/meta/0021_snapshot.json new file mode 100644 index 0000000..927c862 --- /dev/null +++ b/apps/api/src/database/migrations/meta/0021_snapshot.json @@ -0,0 +1,2041 @@ +{ + "id": "3d048773-e7a8-4ba5-920a-fa2656b7f6da", + "prevId": "1236a02f-0d6f-4381-9d3a-c79f023908b7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_logs": { + "name": "activity_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "activityLogs_idx": { + "name": "activityLogs_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_logs_user_id_users_id_fk": { + "name": "activity_logs_user_id_users_id_fk", + "tableFrom": "activity_logs", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.roles": { + "name": "roles", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "roles_idx": { + "name": "roles_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_session_token": { + "name": "previous_session_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_idx": { + "name": "sessions_idx", + "columns": [ + { + "expression": "session_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "municipality": { + "name": "municipality", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parish": { + "name": "parish", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_two_factor_enabled": { + "name": "is_two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_idx": { + "name": "users_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_state_states_id_fk": { + "name": "users_state_states_id_fk", + "tableFrom": "users", + "tableTo": "states", + "columnsFrom": [ + "state" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_municipality_municipalities_id_fk": { + "name": "users_municipality_municipalities_id_fk", + "tableFrom": "users", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_parish_parishes_id_fk": { + "name": "users_parish_parishes_id_fk", + "tableFrom": "users", + "tableTo": "parishes", + "columnsFrom": [ + "parish" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.user_role": { + "name": "user_role", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_role_idx": { + "name": "user_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_role_user_id_users_id_fk": { + "name": "user_role_user_id_users_id_fk", + "tableFrom": "user_role", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_roles_id_fk": { + "name": "user_role_role_id_roles_id_fk", + "tableFrom": "user_role", + "tableTo": "roles", + "schemaTo": "auth", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verificationToken": { + "name": "verificationToken", + "schema": "auth", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.category_type": { + "name": "category_type", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group": { + "name": "group", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "category_typeIx0": { + "name": "category_typeIx0", + "columns": [ + { + "expression": "group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "category_typeIx1": { + "name": "category_typeIx1", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.localities": { + "name": "localities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parish_id": { + "name": "parish_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "localities_index_03": { + "name": "localities_index_03", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_00": { + "name": "localities_index_00", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_01": { + "name": "localities_index_01", + "columns": [ + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_02": { + "name": "localities_index_02", + "columns": [ + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "localities_state_id_states_id_fk": { + "name": "localities_state_id_states_id_fk", + "tableFrom": "localities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_municipality_id_municipalities_id_fk": { + "name": "localities_municipality_id_municipalities_id_fk", + "tableFrom": "localities", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_parish_id_parishes_id_fk": { + "name": "localities_parish_id_parishes_id_fk", + "tableFrom": "localities", + "tableTo": "parishes", + "columnsFrom": [ + "parish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "localities_name_unique": { + "name": "localities_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.municipalities": { + "name": "municipalities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "municipalities_index_00": { + "name": "municipalities_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "municipalities_state_id_states_id_fk": { + "name": "municipalities_state_id_states_id_fk", + "tableFrom": "municipalities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.parishes": { + "name": "parishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "parishes_index_00": { + "name": "parishes_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "parishes_municipality_id_municipalities_id_fk": { + "name": "parishes_municipality_id_municipalities_id_fk", + "tableFrom": "parishes", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.states": { + "name": "states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "states_index_00": { + "name": "states_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url_img": { + "name": "url_img", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gallery": { + "name": "gallery", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'BORRADOR'" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_user_id_users_id_fk": { + "name": "products_user_id_users_id_fk", + "tableFrom": "products", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.answers_surveys": { + "name": "answers_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "answers": { + "name": "answers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "answers_index_00": { + "name": "answers_index_00", + "columns": [ + { + "expression": "answers", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_01": { + "name": "answers_index_01", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_02": { + "name": "answers_index_02", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "answers_surveys_survey_id_surveys_id_fk": { + "name": "answers_surveys_survey_id_surveys_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "answers_surveys_user_id_users_id_fk": { + "name": "answers_surveys_user_id_users_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.surveys": { + "name": "surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_audience": { + "name": "target_audience", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "questions": { + "name": "questions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "surveys_index_00": { + "name": "surveys_index_00", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.training_surveys": { + "name": "training_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastname": { + "name": "lastname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visit_date": { + "name": "visit_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "coor_phone": { + "name": "coor_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "municipality": { + "name": "municipality", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parish": { + "name": "parish", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "osp_type": { + "name": "osp_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "eco_sector": { + "name": "eco_sector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "productive_sector": { + "name": "productive_sector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "central_productive_activity": { + "name": "central_productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "main_productive_activity": { + "name": "main_productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "productive_activity": { + "name": "productive_activity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_rif": { + "name": "osp_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "osp_name": { + "name": "osp_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "company_constitution_year": { + "name": "company_constitution_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_status": { + "name": "current_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVA'" + }, + "infrastructure_mt2": { + "name": "infrastructure_mt2", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "has_transport": { + "name": "has_transport", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "structure_type": { + "name": "structure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_open_space": { + "name": "is_open_space", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "paralysis_reason": { + "name": "paralysis_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "equipment_list": { + "name": "equipment_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "production_list": { + "name": "production_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "product_list": { + "name": "product_list", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "osp_address": { + "name": "osp_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_google_maps_link": { + "name": "osp_google_maps_link", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_name": { + "name": "commune_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "situr_code_commune": { + "name": "situr_code_commune", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "commune_rif": { + "name": "commune_rif", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_spokesperson_name": { + "name": "commune_spokesperson_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_spokesperson_cedula": { + "name": "commune_spokesperson_cedula", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commune_spokesperson_rif": { + "name": "commune_spokesperson_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commune_spokesperson_phone": { + "name": "commune_spokesperson_phone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "commune_email": { + "name": "commune_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council": { + "name": "communal_council", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "situr_code_communal_council": { + "name": "situr_code_communal_council", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "communal_council_rif": { + "name": "communal_council_rif", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_spokesperson_name": { + "name": "communal_council_spokesperson_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_spokesperson_cedula": { + "name": "communal_council_spokesperson_cedula", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council_spokesperson_rif": { + "name": "communal_council_spokesperson_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communal_council_spokesperson_phone": { + "name": "communal_council_spokesperson_phone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "communal_council_email": { + "name": "communal_council_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "osp_responsible_fullname": { + "name": "osp_responsible_fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_cedula": { + "name": "osp_responsible_cedula", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_rif": { + "name": "osp_responsible_rif", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "civil_state": { + "name": "civil_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "osp_responsible_phone": { + "name": "osp_responsible_phone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "osp_responsible_email": { + "name": "osp_responsible_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_burden": { + "name": "family_burden", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "number_of_children": { + "name": "number_of_children", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "general_observations": { + "name": "general_observations", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo1": { + "name": "photo1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo2": { + "name": "photo2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo3": { + "name": "photo3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "training_surveys_index_00": { + "name": "training_surveys_index_00", + "columns": [ + { + "expression": "firstname", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "training_surveys_state_states_id_fk": { + "name": "training_surveys_state_states_id_fk", + "tableFrom": "training_surveys", + "tableTo": "states", + "columnsFrom": [ + "state" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_municipality_municipalities_id_fk": { + "name": "training_surveys_municipality_municipalities_id_fk", + "tableFrom": "training_surveys", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_parish_parishes_id_fk": { + "name": "training_surveys_parish_parishes_id_fk", + "tableFrom": "training_surveys", + "tableTo": "parishes", + "columnsFrom": [ + "parish" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "training_surveys_created_by_users_id_fk": { + "name": "training_surveys_created_by_users_id_fk", + "tableFrom": "training_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "training_surveys_updated_by_users_id_fk": { + "name": "training_surveys_updated_by_users_id_fk", + "tableFrom": "training_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "auth.gender": { + "name": "gender", + "schema": "auth", + "values": [ + "FEMENINO", + "MASCULINO" + ] + }, + "public.nationality": { + "name": "nationality", + "schema": "public", + "values": [ + "VENEZOLANO", + "EXTRANJERO" + ] + }, + "auth.status": { + "name": "status", + "schema": "auth", + "values": [ + "ACTIVE", + "INACTIVE" + ] + } + }, + "schemas": { + "auth": "auth" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "auth.user_access_view": { + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n u.id AS user_id,\n u.username,\n u.email,\n u.fullname,\n r.id AS role_id,\n r.name AS role_name\nFROM\n auth.users u\nLEFT JOIN\n auth.user_role ur ON u.id = ur.user_id \nLEFT JOIN\n auth.roles r ON ur.role_id = r.id", + "name": "user_access_view", + "schema": "auth", + "isExisting": false, + "materialized": false + }, + "public.v_product_store": { + "columns": { + "product_id": { + "name": "product_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url_img": { + "name": "url_img", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gallery": { + "name": "gallery", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.gallery, p.address, p.status, p.user_id, u.fullname, u.email, u.phone\n from products p\n left join auth.users as u on u.id = p.user_id", + "name": "v_product_store", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.v_surveys": { + "columns": { + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "target_audience": { + "name": "target_audience", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "definition": "select id as survey_id, title, description, created_at, closing_date, target_audience from surveys\nwhere published = true", + "name": "v_surveys", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/src/database/migrations/meta/_journal.json b/apps/api/src/database/migrations/meta/_journal.json index 4cf20db..bdb2b61 100644 --- a/apps/api/src/database/migrations/meta/_journal.json +++ b/apps/api/src/database/migrations/meta/_journal.json @@ -141,6 +141,20 @@ "when": 1771858973096, "tag": "0019_cuddly_cobalt_man", "breakpoints": true + }, + { + "idx": 20, + "version": "7", + "when": 1771897944334, + "tag": "0020_certain_bushwacker", + "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1771901546945, + "tag": "0021_warm_machine_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/api/src/database/schema/surveys.ts b/apps/api/src/database/schema/surveys.ts index 2a1d9ec..905b6f5 100644 --- a/apps/api/src/database/schema/surveys.ts +++ b/apps/api/src/database/schema/surveys.ts @@ -77,8 +77,8 @@ export const trainingSurveys = t.pgTable( .notNull() .default(''), productiveActivity: t.text('productive_activity').notNull(), - ospRif: t.text('osp_rif').notNull(), - ospName: t.text('osp_name').notNull(), + ospRif: t.text('osp_rif'), + ospName: t.text('osp_name'), companyConstitutionYear: t.integer('company_constitution_year').notNull(), currentStatus: t.text('current_status').notNull().default('ACTIVA'), infrastructureMt2: t.text('infrastructure_mt2').notNull().default(''), @@ -98,10 +98,8 @@ export const trainingSurveys = t.pgTable( .text('commune_spokesperson_name') .notNull() .default(''), - communeSpokespersonCedula: t - .text('commune_spokesperson_cedula'), - communeSpokespersonRif: t - .text('commune_spokesperson_rif'), + communeSpokespersonCedula: t.text('commune_spokesperson_cedula'), + communeSpokespersonRif: t.text('commune_spokesperson_rif'), communeSpokespersonPhone: t .text('commune_spokesperson_phone') .notNull() @@ -114,10 +112,10 @@ export const trainingSurveys = t.pgTable( .text('communal_council_spokesperson_name') .notNull() .default(''), - communalCouncilSpokespersonCedula: t - .text('communal_council_spokesperson_cedula'), - communalCouncilSpokespersonRif: t - .text('communal_council_spokesperson_rif'), + communalCouncilSpokespersonCedula: t.text( + 'communal_council_spokesperson_cedula', + ), + communalCouncilSpokespersonRif: t.text('communal_council_spokesperson_rif'), communalCouncilSpokespersonPhone: t .text('communal_council_spokesperson_phone') .notNull() @@ -128,20 +126,24 @@ export const trainingSurveys = t.pgTable( .default(''), ospResponsibleFullname: t.text('osp_responsible_fullname').notNull(), ospResponsibleCedula: t.text('osp_responsible_cedula').notNull(), - ospResponsibleRif: t.text('osp_responsible_rif').notNull(), - civilState: t.text('civil_state').notNull(), + ospResponsibleRif: t.text('osp_responsible_rif'), + civilState: t.text('civil_state'), ospResponsiblePhone: t.text('osp_responsible_phone').notNull(), - ospResponsibleEmail: t.text('osp_responsible_email').notNull(), - familyBurden: t.integer('family_burden').notNull(), - numberOfChildren: t.integer('number_of_children').notNull(), + ospResponsibleEmail: t.text('osp_responsible_email'), + familyBurden: t.integer('family_burden'), + numberOfChildren: t.integer('number_of_children'), generalObservations: t.text('general_observations'), // Fotos photo1: t.text('photo1'), photo2: t.text('photo2'), photo3: t.text('photo3'), // informacion del usuario que creo y actualizo el registro - createdBy: t.integer('created_by').references(() => users.id, { onDelete: 'cascade' }), - updatedBy: t.integer('updated_by').references(() => users.id, { onDelete: 'cascade' }), + createdBy: t + .integer('created_by') + .references(() => users.id, { onDelete: 'cascade' }), + updatedBy: t + .integer('updated_by') + .references(() => users.id, { onDelete: 'cascade' }), ...timestamps, }, (trainingSurveys) => ({ diff --git a/apps/api/src/features/training/dto/create-training.dto.ts b/apps/api/src/features/training/dto/create-training.dto.ts index f6cda39..14af375 100644 --- a/apps/api/src/features/training/dto/create-training.dto.ts +++ b/apps/api/src/features/training/dto/create-training.dto.ts @@ -4,9 +4,11 @@ import { IsArray, IsBoolean, IsDateString, + IsEmail, IsInt, IsOptional, IsString, + ValidateIf, } from 'class-validator'; export class CreateTrainingDto { @@ -124,6 +126,7 @@ export class CreateTrainingDto { @ApiProperty() @IsString() + @IsOptional() ospResponsibleRif: string; @ApiProperty() @@ -131,20 +134,25 @@ export class CreateTrainingDto { ospResponsiblePhone: string; @ApiProperty() - @IsString() - ospResponsibleEmail: string; + @IsOptional() + @ValidateIf((o, v) => v !== '' && v !== null && v !== undefined) + @IsEmail() + ospResponsibleEmail?: string; @ApiProperty() @IsString() + @IsOptional() civilState: string; @ApiProperty() @IsInt() + @IsOptional() @Type(() => Number) // Convierte "3" -> 3 familyBurden: number; @ApiProperty() @IsInt() + @IsOptional() @Type(() => Number) numberOfChildren: number; @@ -165,14 +173,15 @@ export class CreateTrainingDto { @IsString() communeSpokespersonName: string; - @ApiProperty() @IsString() communeSpokespersonPhone: string; @ApiProperty() @IsOptional() - communeEmail: string; + @ValidateIf((o, v) => v !== '' && v !== null && v !== undefined) + @IsEmail() + communeEmail?: string; @ApiProperty() @IsString() @@ -195,10 +204,10 @@ export class CreateTrainingDto { communalCouncilSpokespersonPhone: string; @ApiProperty() - @IsString() - communalCouncilEmail: string; - - + @IsOptional() + @ValidateIf((o, v) => v !== '' && v !== null && v !== undefined) + @IsEmail() + communalCouncilEmail?: string; // === 6. LISTAS (Arrays JSON) === // Reciben un string JSON '[{...}]' y lo convierten a Objeto JS real @@ -248,13 +257,11 @@ export class CreateTrainingDto { }) productList?: any[]; - - //ubicacion + //ubicacion @ApiProperty() @IsString() state: string; - @ApiProperty() @IsString() municipality: string; diff --git a/apps/api/src/features/training/training.controller.ts b/apps/api/src/features/training/training.controller.ts index a39a3d9..85c23a8 100644 --- a/apps/api/src/features/training/training.controller.ts +++ b/apps/api/src/features/training/training.controller.ts @@ -7,12 +7,9 @@ import { Patch, Post, Query, - Res, + Req, UploadedFiles, UseInterceptors, - StreamableFile, - Header, - Req } from '@nestjs/common'; import { FilesInterceptor } from '@nestjs/platform-express'; import { @@ -27,30 +24,29 @@ import { CreateTrainingDto } from './dto/create-training.dto'; import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto'; import { UpdateTrainingDto } from './dto/update-training.dto'; import { TrainingService } from './training.service'; -import { Public } from '@/common/decorators'; @ApiTags('training') @Controller('training') export class TrainingController { - constructor(private readonly trainingService: TrainingService) { } + constructor(private readonly trainingService: TrainingService) {} - @Public() - @Get('export/:id') - @ApiOperation({ summary: 'Export training template' }) - @ApiResponse({ - status: 200, - description: 'Return training template.', - content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } } - }) - @Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') - @Header('Content-Disposition', 'attachment; filename=export_osp.xlsx') - async exportTemplate(@Param('id') id: string) { - if (!Number(id)) { - throw new Error('ID is required'); - } - const data = await this.trainingService.exportTemplate(Number(id)); - return new StreamableFile(data); - } + // @Public() + // @Get('export/:id') + // @ApiOperation({ summary: 'Export training template' }) + // @ApiResponse({ + // status: 200, + // description: 'Return training template.', + // content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } } + // }) + // @Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + // @Header('Content-Disposition', 'attachment; filename=export_osp.xlsx') + // async exportTemplate(@Param('id') id: string) { + // if (!Number(id)) { + // throw new Error('ID is required'); + // } + // const data = await this.trainingService.exportTemplate(Number(id)); + // return new StreamableFile(data); + // } @Get() @ApiOperation({ @@ -100,7 +96,11 @@ export class TrainingController { @UploadedFiles(ImageProcessingPipe) files: Express.Multer.File[], ) { const userId = (req as any).user?.id; - const data = await this.trainingService.create(createTrainingDto, files, userId); + const data = await this.trainingService.create( + createTrainingDto, + files, + userId, + ); return { message: 'Training record created successfully', data }; } diff --git a/apps/api/src/features/training/training.service.ts b/apps/api/src/features/training/training.service.ts index 4f31a20..dc73df8 100644 --- a/apps/api/src/features/training/training.service.ts +++ b/apps/api/src/features/training/training.service.ts @@ -1,12 +1,11 @@ -import { HttpException, HttpStatus, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { MinioService } from '@/common/minio/minio.service'; +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { and, eq, gte, ilike, lte, or, SQL, sql } from 'drizzle-orm'; import { NodePgDatabase } from 'drizzle-orm/node-postgres'; -import * as fs from 'fs'; -import * as path from 'path'; import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider'; import * as schema from 'src/database/index'; -import { municipalities, parishes, states, trainingSurveys } from 'src/database/index'; -import XlsxPopulate from 'xlsx-populate'; +import { states, trainingSurveys } from 'src/database/index'; + import { PaginationDto } from '../../common/dto/pagination.dto'; import { CreateTrainingDto } from './dto/create-training.dto'; import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto'; @@ -16,7 +15,8 @@ import { UpdateTrainingDto } from './dto/update-training.dto'; export class TrainingService { constructor( @Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase, - ) { } + private readonly minioService: MinioService, + ) {} async findAll(paginationDto?: PaginationDto) { const { @@ -232,33 +232,33 @@ export class TrainingService { private async saveFiles(files: Express.Multer.File[]): Promise { if (!files || files.length === 0) return []; - const uploadDir = './uploads/training'; - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); - } - const savedPaths: string[] = []; for (const file of files) { - const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname)}`; - const filePath = path.join(uploadDir, fileName); - fs.writeFileSync(filePath, file.buffer); - savedPaths.push(`/assets/training/${fileName}`); + const objectName = await this.minioService.upload(file, 'training'); + const fileUrl = this.minioService.getPublicUrl(objectName); + savedPaths.push(fileUrl); } return savedPaths; } - private deleteFile(assetPath: string) { - if (!assetPath) return; - // Map /assets/training/filename.webp back to ./uploads/training/filename.webp - const relativePath = assetPath.replace('/assets/training/', ''); - const fullPath = path.join('./uploads/training', relativePath); + private async deleteFile(fileUrl: string) { + if (!fileUrl) return; - if (fs.existsSync(fullPath)) { - try { - fs.unlinkSync(fullPath); - } catch (err) { - console.error(`Error deleting file ${fullPath}:`, err); - } + // Extract object name from URL + // URL format: http://endpoint:port/bucket/folder/filename + // Or it could be just the path if we decided that. + // Assuming fileUrl is the full public URL from getPublicUrl + try { + const url = new URL(fileUrl); + const pathname = url.pathname; // /bucket/folder/filename + const parts = pathname.split('/'); + // parts[0] is '', parts[1] is bucket, parts[2..] is objectName + const objectName = parts.slice(2).join('/'); + + await this.minioService.delete(objectName); + } catch (error) { + // If it's not a valid URL, maybe it's just the object name stored from before + await this.minioService.delete(fileUrl); } } @@ -268,11 +268,13 @@ export class TrainingService { userId: number, ) { // 1. Guardar fotos + const photoPaths = await this.saveFiles(files); // 2. Extraer solo visitDate para formatearlo. // Ya NO extraemos state, municipality, etc. porque no vienen en el DTO. - const { visitDate, state, municipality, parish, ...rest } = createTrainingDto; + const { visitDate, state, municipality, parish, ...rest } = + createTrainingDto; const [newRecord] = await this.drizzle .insert(trainingSurveys) @@ -305,45 +307,48 @@ export class TrainingService { userId: number, ) { const currentRecord = await this.findOne(id); + const photoFields = ['photo1', 'photo2', 'photo3'] as const; - const photoPaths = await this.saveFiles(files); + // 1. Guardar fotos nuevas en MinIO + const newFilePaths = await this.saveFiles(files); const updateData: any = { ...updateTrainingDto }; - // Handle photo updates/removals - const photoFields = ['photo1', 'photo2', 'photo3'] as const; - - // 1. First, handle explicit deletions (where field is '') - photoFields.forEach((field) => { - if (updateData[field] === '') { - const oldPath = currentRecord[field]; - if (oldPath) this.deleteFile(oldPath); - updateData[field] = null; + // 2. Determinar el estado final de las fotos (diff) + // - Si el DTO tiene un valor (URL existente o ''), lo usamos. + // - Si el DTO no tiene el campo (undefined), mantenemos el de la DB. + const finalPhotos: (string | null)[] = photoFields.map((field) => { + const dtoValue = updateData[field]; + if (dtoValue !== undefined) { + return dtoValue === '' ? null : dtoValue; } + return currentRecord[field]; }); - // 2. We need to find which slots are currently "available" (null) after deletions - // and which ones have existing URLs that we want to keep. - - // Let's determine the final state of the 3 slots. - const finalPhotos: (string | null)[] = [ - updateData.photo1 !== undefined ? updateData.photo1 : currentRecord.photo1, - updateData.photo2 !== undefined ? updateData.photo2 : currentRecord.photo2, - updateData.photo3 !== undefined ? updateData.photo3 : currentRecord.photo3, - ]; - - // 3. Fill the available (null) slots with NEW photo paths - if (photoPaths.length > 0) { - let photoPathIdx = 0; - for (let i = 0; i < 3 && photoPathIdx < photoPaths.length; i++) { + // 3. Asignar los nuevos paths subidos a los slots que quedaron vacíos + if (newFilePaths.length > 0) { + let newIdx = 0; + for (let i = 0; i < 3 && newIdx < newFilePaths.length; i++) { if (!finalPhotos[i]) { - finalPhotos[i] = photoPaths[photoPathIdx]; - photoPathIdx++; + finalPhotos[i] = newFilePaths[newIdx]; + newIdx++; } } } - // Assign back to updateData + // 4. LIMPIEZA: Borrar de MinIO los archivos que ya no están en ningún slot + const oldPhotos = photoFields + .map((f) => currentRecord[f]) + .filter((p): p is string => Boolean(p)); + const newPhotosSet = new Set(finalPhotos.filter(Boolean)); + + for (const oldPath of oldPhotos) { + if (!newPhotosSet.has(oldPath)) { + await this.deleteFile(oldPath); + } + } + + // 5. Preparar datos finales para la DB updateData.photo1 = finalPhotos[0]; updateData.photo2 = finalPhotos[1]; updateData.photo3 = finalPhotos[2]; @@ -368,9 +373,9 @@ export class TrainingService { const record = await this.findOne(id); // Delete associated files - if (record.photo1) this.deleteFile(record.photo1); - if (record.photo2) this.deleteFile(record.photo2); - if (record.photo3) this.deleteFile(record.photo3); + if (record.photo1) await this.deleteFile(record.photo1); + if (record.photo2) await this.deleteFile(record.photo2); + if (record.photo3) await this.deleteFile(record.photo3); const [deletedRecord] = await this.drizzle .delete(trainingSurveys) @@ -499,139 +504,182 @@ export class TrainingService { // return await workbook.outputAsync(); // } - async exportTemplate(id: number) { + // async exportTemplate(id: number) { + // // Validar que el registro exista + // const exist = await this.findOne(id); + // if (!exist) throw new NotFoundException(`No se encontro el registro`); - // Validar que el registro exista - const exist = await this.findOne(id); - if (!exist) throw new NotFoundException(`No se encontro el registro`); + // // Obtener los datos del registro + // const records = await this.drizzle + // .select({ + // // id: trainingSurveys.id, + // visitDate: trainingSurveys.visitDate, + // ospName: trainingSurveys.ospName, + // productiveSector: trainingSurveys.productiveSector, + // ospAddress: trainingSurveys.ospAddress, + // ospRif: trainingSurveys.ospRif, - // Obtener los datos del registro - const records = await this.drizzle - .select({ - // id: trainingSurveys.id, - visitDate: trainingSurveys.visitDate, - ospName: trainingSurveys.ospName, - productiveSector: trainingSurveys.productiveSector, - ospAddress: trainingSurveys.ospAddress, - ospRif: trainingSurveys.ospRif, + // siturCodeCommune: trainingSurveys.siturCodeCommune, + // communeEmail: trainingSurveys.communeEmail, + // communeRif: trainingSurveys.communeRif, + // communeSpokespersonName: trainingSurveys.communeSpokespersonName, + // communeSpokespersonPhone: trainingSurveys.communeSpokespersonPhone, - siturCodeCommune: trainingSurveys.siturCodeCommune, - communeEmail: trainingSurveys.communeEmail, - communeRif: trainingSurveys.communeRif, - communeSpokespersonName: trainingSurveys.communeSpokespersonName, - communeSpokespersonPhone: trainingSurveys.communeSpokespersonPhone, + // siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil, + // communalCouncilRif: trainingSurveys.communalCouncilRif, + // communalCouncilSpokespersonName: + // trainingSurveys.communalCouncilSpokespersonName, + // communalCouncilSpokespersonPhone: + // trainingSurveys.communalCouncilSpokespersonPhone, - siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil, - communalCouncilRif: trainingSurveys.communalCouncilRif, - communalCouncilSpokespersonName: trainingSurveys.communalCouncilSpokespersonName, - communalCouncilSpokespersonPhone: trainingSurveys.communalCouncilSpokespersonPhone, + // ospType: trainingSurveys.ospType, + // productiveActivity: trainingSurveys.productiveActivity, // Sector Productivo + // companyConstitutionYear: trainingSurveys.companyConstitutionYear, + // infrastructureMt2: trainingSurveys.infrastructureMt2, - ospType: trainingSurveys.ospType, - productiveActivity: trainingSurveys.productiveActivity, // Sector Productivo - companyConstitutionYear: trainingSurveys.companyConstitutionYear, - infrastructureMt2: trainingSurveys.infrastructureMt2, + // hasTransport: trainingSurveys.hasTransport, + // structureType: trainingSurveys.structureType, + // isOpenSpace: trainingSurveys.isOpenSpace, - hasTransport: trainingSurveys.hasTransport, - structureType: trainingSurveys.structureType, - isOpenSpace: trainingSurveys.isOpenSpace, + // ospResponsibleFullname: trainingSurveys.ospResponsibleFullname, + // ospResponsibleCedula: trainingSurveys.ospResponsibleCedula, + // ospResponsiblePhone: trainingSurveys.ospResponsiblePhone, - ospResponsibleFullname: trainingSurveys.ospResponsibleFullname, - ospResponsibleCedula: trainingSurveys.ospResponsibleCedula, - ospResponsiblePhone: trainingSurveys.ospResponsiblePhone, + // productList: trainingSurveys.productList, + // equipmentList: trainingSurveys.equipmentList, + // productionList: trainingSurveys.productionList, - productList: trainingSurveys.productList, - equipmentList: trainingSurveys.equipmentList, - productionList: trainingSurveys.productionList, + // // photo1: trainingSurveys.photo1 + // }) + // .from(trainingSurveys) + // .where(eq(trainingSurveys.id, id)); + // // .leftJoin(states, eq(trainingSurveys.state, states.id)) + // // .leftJoin(municipalities,eq(trainingSurveys.municipality, municipalities.id)) + // // .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id)) - // photo1: trainingSurveys.photo1 - }) - .from(trainingSurveys) - .where(eq(trainingSurveys.id, id)) - // .leftJoin(states, eq(trainingSurveys.state, states.id)) - // .leftJoin(municipalities,eq(trainingSurveys.municipality, municipalities.id)) - // .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id)) + // let equipmentList: any[] = Array.isArray(records[0].equipmentList) + // ? records[0].equipmentList + // : []; + // let productList: any[] = Array.isArray(records[0].productList) + // ? records[0].productList + // : []; + // let productionList: any[] = Array.isArray(records[0].productionList) + // ? records[0].productionList + // : []; - let equipmentList: any[] = Array.isArray(records[0].equipmentList) ? records[0].equipmentList : []; - let productList: any[] = Array.isArray(records[0].productList) ? records[0].productList : []; - let productionList: any[] = Array.isArray(records[0].productionList) ? records[0].productionList : []; + // console.log('equipmentList', equipmentList); + // console.log('productList', productList); + // console.log('productionList', productionList); - console.log('equipmentList', equipmentList); - console.log('productList', productList); - console.log('productionList', productionList); + // let equipmentListArray: any[] = []; + // let productListArray: any[] = []; + // let productionListArray: any[] = []; - let equipmentListArray: any[] = []; - let productListArray: any[] = []; - let productionListArray: any[] = []; + // const equipmentListCount = equipmentList.length; + // for (let i = 0; i < equipmentListCount; i++) { + // equipmentListArray.push([ + // equipmentList[i].machine, + // '', + // equipmentList[i].quantity, + // ]); + // } - const equipmentListCount = equipmentList.length; - for (let i = 0; i < equipmentListCount; i++) { - equipmentListArray.push([equipmentList[i].machine, '', equipmentList[i].quantity]); - } + // const productListCount = productList.length; + // for (let i = 0; i < productListCount; i++) { + // productListArray.push([ + // productList[i].productName, + // productList[i].dailyCount, + // productList[i].weeklyCount, + // productList[i].monthlyCount, + // ]); + // } - const productListCount = productList.length; - for (let i = 0; i < productListCount; i++) { - productListArray.push([productList[i].productName, productList[i].dailyCount, productList[i].weeklyCount, productList[i].monthlyCount]); - } + // const productionListCount = productionList.length; + // for (let i = 0; i < productionListCount; i++) { + // productionListArray.push([ + // productionList[i].rawMaterial, + // '', + // productionList[i].quantity, + // ]); + // } - const productionListCount = productionList.length; - for (let i = 0; i < productionListCount; i++) { - productionListArray.push([productionList[i].rawMaterial, '', productionList[i].quantity]); - } + // // Ruta de la plantilla + // const templatePath = path.join( + // __dirname, + // 'export_template', + // 'excel.osp.xlsx', + // ); - // Ruta de la plantilla - const templatePath = path.join( - __dirname, - 'export_template', - 'excel.osp.xlsx', - ); + // // Cargar la plantilla + // const book = await XlsxPopulate.fromFileAsync(templatePath); - // Cargar la plantilla - const book = await XlsxPopulate.fromFileAsync(templatePath); + // const isoString = records[0].visitDate; + // const dateObj = new Date(isoString); + // const fechaFormateada = dateObj.toLocaleDateString('es-ES'); + // const horaFormateada = dateObj.toLocaleTimeString('es-ES', { + // hour: '2-digit', + // minute: '2-digit', + // }); - const isoString = records[0].visitDate; - const dateObj = new Date(isoString); - const fechaFormateada = dateObj.toLocaleDateString('es-ES'); - const horaFormateada = dateObj.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }); + // // Llenar los datos + // book.sheet(0).cell('A6').value(records[0].productiveSector); + // book.sheet(0).cell('D6').value(records[0].ospName); + // book.sheet(0).cell('L5').value(fechaFormateada); + // book.sheet(0).cell('L6').value(horaFormateada); + // book.sheet(0).cell('B10').value(records[0].ospAddress); + // book.sheet(0).cell('C11').value(records[0].communeEmail); + // book.sheet(0).cell('C12').value(records[0].communeSpokespersonName); + // book.sheet(0).cell('G11').value(records[0].communeRif); + // book.sheet(0).cell('G12').value(records[0].communeSpokespersonPhone); + // book.sheet(0).cell('C13').value(records[0].siturCodeCommune); + // book.sheet(0).cell('G13').value(records[0].siturCodeCommunalCouncil); + // book.sheet(0).cell('G14').value(records[0].communalCouncilRif); + // book.sheet(0).cell('C15').value(records[0].communalCouncilSpokespersonName); + // book + // .sheet(0) + // .cell('G15') + // .value(records[0].communalCouncilSpokespersonPhone); + // book.sheet(0).cell('C16').value(records[0].ospType); + // book.sheet(0).cell('C17').value(records[0].ospName); + // book.sheet(0).cell('C18').value(records[0].productiveActivity); + // book.sheet(0).cell('C19').value('Proveedores'); + // book.sheet(0).cell('C20').value(records[0].companyConstitutionYear); + // book.sheet(0).cell('C21').value(records[0].infrastructureMt2); + // book.sheet(0).cell('G17').value(records[0].ospRif); - // Llenar los datos - book.sheet(0).cell('A6').value(records[0].productiveSector); - book.sheet(0).cell('D6').value(records[0].ospName); - book.sheet(0).cell('L5').value(fechaFormateada); - book.sheet(0).cell('L6').value(horaFormateada); - book.sheet(0).cell('B10').value(records[0].ospAddress); - book.sheet(0).cell('C11').value(records[0].communeEmail); - book.sheet(0).cell('C12').value(records[0].communeSpokespersonName); - book.sheet(0).cell('G11').value(records[0].communeRif); - book.sheet(0).cell('G12').value(records[0].communeSpokespersonPhone); - book.sheet(0).cell('C13').value(records[0].siturCodeCommune); - book.sheet(0).cell('G13').value(records[0].siturCodeCommunalCouncil); - book.sheet(0).cell('G14').value(records[0].communalCouncilRif); - book.sheet(0).cell('C15').value(records[0].communalCouncilSpokespersonName); - book.sheet(0).cell('G15').value(records[0].communalCouncilSpokespersonPhone); - book.sheet(0).cell('C16').value(records[0].ospType); - book.sheet(0).cell('C17').value(records[0].ospName); - book.sheet(0).cell('C18').value(records[0].productiveActivity); - book.sheet(0).cell('C19').value('Proveedores'); - book.sheet(0).cell('C20').value(records[0].companyConstitutionYear); - book.sheet(0).cell('C21').value(records[0].infrastructureMt2); - book.sheet(0).cell('G17').value(records[0].ospRif); + // book + // .sheet(0) + // .cell(records[0].hasTransport === true ? 'J19' : 'L19') + // .value('X'); + // book + // .sheet(0) + // .cell(records[0].structureType === 'CASA' ? 'J20' : 'L20') + // .value('X'); + // book + // .sheet(0) + // .cell(records[0].isOpenSpace === true ? 'J21' : 'L21') + // .value('X'); - book.sheet(0).cell(records[0].hasTransport === true ? 'J19' : 'L19').value('X'); - book.sheet(0).cell(records[0].structureType === 'CASA' ? 'J20' : 'L20').value('X'); - book.sheet(0).cell(records[0].isOpenSpace === true ? 'J21' : 'L21').value('X'); + // book.sheet(0).cell('A24').value(records[0].ospResponsibleFullname); + // book.sheet(0).cell('C24').value(records[0].ospResponsibleCedula); + // book.sheet(0).cell('E24').value(records[0].ospResponsiblePhone); - book.sheet(0).cell('A24').value(records[0].ospResponsibleFullname); - book.sheet(0).cell('C24').value(records[0].ospResponsibleCedula); - book.sheet(0).cell('E24').value(records[0].ospResponsiblePhone); + // book.sheet(0).cell('J24').value('N Femenino'); + // book.sheet(0).cell('L24').value('N Masculino'); + // book + // .sheet(0) + // .range(`A28:C${equipmentListCount + 28}`) + // .value(equipmentListArray); + // book + // .sheet(0) + // .range(`E28:G${productionListCount + 28}`) + // .value(productionListArray); + // book + // .sheet(0) + // .range(`I28:L${productListCount + 28}`) + // .value(productListArray); - book.sheet(0).cell('J24').value('N Femenino'); - book.sheet(0).cell('L24').value('N Masculino'); - - book.sheet(0).range(`A28:C${equipmentListCount + 28}`).value(equipmentListArray); - book.sheet(0).range(`E28:G${productionListCount + 28}`).value(productionListArray); - book.sheet(0).range(`I28:L${productListCount + 28}`).value(productListArray); - - return book.outputAsync(); - } + // return book.outputAsync(); + // } } diff --git a/apps/web/feactures/training/components/equipment-list.tsx b/apps/web/feactures/training/components/equipment-list.tsx index e882bce..c26ac7c 100644 --- a/apps/web/feactures/training/components/equipment-list.tsx +++ b/apps/web/feactures/training/components/equipment-list.tsx @@ -20,22 +20,33 @@ import { Label } from '@repo/shadcn/label'; import { Trash2 } from 'lucide-react'; import { useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; +import { TrainingSchema } from '../schemas/training'; + +interface EquipmentItem { + machine: string; + quantity: string | number; +} export function EquipmentList() { - const { control, register } = useFormContext(); + const { control, register } = useFormContext(); const { fields, append, remove } = useFieldArray({ control, name: 'equipmentList', }); const [isOpen, setIsOpen] = useState(false); - const [newItem, setNewItem] = useState({ + const [newItem, setNewItem] = useState({ machine: '', quantity: '', }); - const handleAdd = () => { - if (newItem.machine && newItem.quantity) { - append({ ...newItem, quantity: Number(newItem.quantity) }); + const handleAdd = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (newItem.machine.trim()) { + append({ + machine: newItem.machine, + quantity: newItem.quantity ? Number(newItem.quantity) : 0, + }); setNewItem({ machine: '', quantity: '' }); setIsOpen(false); } @@ -47,9 +58,11 @@ export function EquipmentList() {

Datos del Equipamiento

- + - + e.preventDefault()}> Agregar Maquinaria/Equipo @@ -58,8 +71,9 @@ export function EquipmentList() {
- + setNewItem({ ...newItem, machine: e.target.value }) @@ -68,8 +82,9 @@ export function EquipmentList() { />
- + @@ -82,12 +97,17 @@ export function EquipmentList() { - +
@@ -99,7 +119,6 @@ export function EquipmentList() { Maquinaria - Especificaciones Cantidad @@ -111,23 +130,27 @@ export function EquipmentList() { - {/* @ts-ignore */} {field.machine} - {/* @ts-ignore */} {field.quantity} @@ -137,7 +160,7 @@ export function EquipmentList() { {fields.length === 0 && ( No hay equipamiento registrado diff --git a/apps/web/feactures/training/components/form.tsx b/apps/web/feactures/training/components/form.tsx index dc86585..00c379f 100644 --- a/apps/web/feactures/training/components/form.tsx +++ b/apps/web/feactures/training/components/form.tsx @@ -162,14 +162,16 @@ export function CreateTrainingForm({ }); // 1. Extrae errors de formState -const { formState: { errors } } = form; + const { + formState: { errors }, + } = form; -// 2. Crea un efecto para monitorearlos -useEffect(() => { - if (Object.keys(errors).length > 0) { - console.log("Campos con errores:", errors); - } -}, [errors]); + // 2. Crea un efecto para monitorearlos + useEffect(() => { + if (Object.keys(errors).length > 0) { + console.log('Campos con errores:', errors); + } + }, [errors]); // Cascading Select Logic const ecoSector = useWatch({ control: form.control, name: 'ecoSector' }); @@ -219,13 +221,12 @@ useEffect(() => { { id: 0, name: 'Sin estados' }, ]; - const coorMunicipalityOptions = - dataCoorMunicipality?.data?.length - ? dataCoorMunicipality.data - : [{ id: 0, stateId: 0, name: 'Sin Municipios' }]; + const coorMunicipalityOptions = dataCoorMunicipality?.data?.length + ? dataCoorMunicipality.data + : [{ id: 0, stateId: 0, name: 'Sin Municipios' }]; const coorParishOptions = - Array.isArray(dataCoorParish?.data) && dataCoorParish?.data?.length + Array.isArray(dataCoorParish?.data) && dataCoorParish?.data?.length ? dataCoorParish.data : [{ id: 0, stateId: 0, name: 'Sin Parroquias' }]; @@ -264,8 +265,6 @@ useEffect(() => { }, [defaultValues]); const onSubmit = async (formData: TrainingSchema) => { - - const data = new FormData(); // 1. Definimos las claves que NO queremos enviar en el bucle general @@ -279,12 +278,13 @@ useEffect(() => { ]; Object.entries(formData).forEach(([key, value]) => { - // 2. Condición actualizada: Si la key está en la lista de excluidos, la saltamos + // 2. Condición actualizada: Si la key está en la lista de excluidos, o es un valor vacío (null/undefined), lo saltamos. + // Permitimos cadenas vacías ('') para indicar al backend que se debe limpiar el campo (ej: borrar foto). if (excludedKeys.includes(key) || value === undefined || value === null) { return; } - // 3. Lógica de conversión (Igual que tenías) + // 3. Lógica de conversión if ( Array.isArray(value) || (typeof value === 'object' && !(value instanceof Date)) @@ -393,10 +393,13 @@ useEffect(() => { Teléfono { + const val = e.target.value.replace(/\D/g, ''); + field.onChange(val.slice(0, 11)); + }} /> @@ -514,7 +517,7 @@ useEffect(() => { @@ -715,7 +718,11 @@ useEffect(() => { RIF de la organización (opcional) - + @@ -731,7 +738,7 @@ useEffect(() => { Nombre de la organización (opcional) - + @@ -747,7 +754,11 @@ useEffect(() => { Año de constitución - + @@ -764,7 +775,7 @@ useEffect(() => { + @@ -837,7 +852,7 @@ useEffect(() => { @@ -893,7 +908,7 @@ useEffect(() => { Razones de paralización (opcional) -