diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index 80fa5d0..24b7e96 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -1,4 +1,4 @@ - +require('dotenv').config(); module.exports = { production: { @@ -12,11 +12,12 @@ module.exports = { seederStorage: 'sequelize', }, development: { - username: 'postgres', + username: process.env.DB_USER || 'postgres', dialect: 'postgres', - password: '', - database: 'db_renacer_certificados_saas', + password: process.env.DB_PASS || '', + database: process.env.DB_NAME || 'db_renacer_certificados_saas', host: process.env.DB_HOST || 'localhost', + port: process.env.DB_PORT || 5432, logging: console.log, seederStorage: 'sequelize', }, @@ -30,4 +31,4 @@ module.exports = { logging: console.log, seederStorage: 'sequelize', } -}; +}; \ No newline at end of file diff --git a/backend/src/db/migrations/1772556991799.js b/backend/src/db/migrations/1772556991799.js new file mode 100644 index 0000000..41d029e --- /dev/null +++ b/backend/src/db/migrations/1772556991799.js @@ -0,0 +1,45 @@ +const { v4: uuid } = require('uuid'); + +module.exports = { + async up(queryInterface, Sequelize) { + const createdAt = new Date(); + const updatedAt = new Date(); + + // Get the Public role ID + const [roles] = await queryInterface.sequelize.query( + `SELECT id FROM "roles" WHERE name = 'Public' LIMIT 1` + ); + + if (!roles || roles.length === 0) { + console.error("Public role not found"); + return; + } + + const publicRoleId = roles[0].id; + + // Get permission IDs + const [permissions] = await queryInterface.sequelize.query( + `SELECT id, name FROM "permissions" WHERE name IN ('READ_CERTIFICATES', 'READ_USERS', 'READ_COMMUNITIES')` + ); + + const rolePermissions = permissions.map(p => ({ + createdAt, + updatedAt, + roles_permissionsId: publicRoleId, + permissionId: p.id + })); + + // Use ignoreDuplicates to avoid errors if already present + for (const rp of rolePermissions) { + await queryInterface.sequelize.query( + `INSERT INTO "rolesPermissionsPermissions" ("createdAt", "updatedAt", "roles_permissionsId", "permissionId") + VALUES ('${rp.createdAt.toISOString()}', '${rp.updatedAt.toISOString()}', '${rp.roles_permissionsId}', '${rp.permissionId}') + ON CONFLICT DO NOTHING` + ); + } + }, + + async down(queryInterface, Sequelize) { + // Revert logic + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index ecb1410..2f44294 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -121,7 +121,7 @@ app.use('/api/organization_settings', passport.authenticate('jwt', {session: fal app.use('/api/certificate_templates', passport.authenticate('jwt', {session: false}), certificate_templatesRoutes); -app.use('/api/certificates', passport.authenticate('jwt', {session: false}), certificatesRoutes); +app.use('/api/certificates', certificatesRoutes); app.use('/api/payments_log', passport.authenticate('jwt', {session: false}), payments_logRoutes); diff --git a/frontend/src/config.ts b/frontend/src/config.ts index a9783c8..353e813 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -8,7 +8,7 @@ export const localStorageStyleKey = 'style' export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20' -export const appTitle = 'created by Flatlogic generator!' +export const appTitle = 'Corporación Renacer - DDHH Mundiales' export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}` diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 0507f35..fc57666 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,157 @@ - -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; +import { useRouter } from 'next/router'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import { mdiShieldCheck, mdiAccountGroup, mdiCertificate, mdiMagnify } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; +export default function LandingPage() { + const router = useRouter(); + const [searchCode, setSearchCode] = useState(''); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('right'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Renacer Certificados SaaS' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchCode.trim()) { + router.push(`/verify?code=${searchCode.trim()}`); } }; - return ( -
- - {getPageTitle('Starter Page')} - + return ( +
+ + {getPageTitle('Inicio')} + + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

+ {/* Navigation */} + + + {/* Hero Section */} +
+
+
+

+ Empoderando Líderes Sociales en Defensa de los Derechos Humanos +

+

+ Validamos y certificamos la labor de quienes transforman comunidades. + Garantizamos transparencia, verificabilidad y credibilidad internacional. +

+ + {/* Verification Box */} +
+
+
+ +
+ setSearchCode(e.target.value)} + /> + +
+

¿Tienes un código QR? Escanéalo para verificación instantánea.

+
+
+
- - - - - + {/* Features */} +
+
+
+

Nuestra Misión

+

+ Credibilidad Digital para el Impacto Social +

+
+
+
+
+
+ +
+
Certificación Oficial
+
+

Emitimos certificados digitales con validez anual, respaldando la formación y trayectoria de líderes en DDHH.

+
+
+
+
+ +
+
Verificabilidad Inmediata
+
+

Cualquier entidad puede validar la autenticidad de un certificado mediante QR o código único en tiempo real.

+
+
+
+
+ +
+
Comunidad de Líderes
+
+

Más que un papel, es el acceso a una red global comprometida con la paz y la justicia social en Colombia y el mundo.

+
+
+
+
+
+
+ + {/* Footer */} +
+
+
+ Renacer DDHH +

Corporación Renacer Derechos Humanos Mundiales. NIT: 9018960153. Sede Principal: Colombia.

+
+
+
+

Institucional

+ Sobre Nosotros + Misión y Visión +
+
+

Legal

+ Términos + Privacidad +
+
+
+
+ © 2026 Corporación Renacer DDHH Mundiales. Todos los derechos reservados. +
+
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
- -
- ); + ); } -Starter.getLayout = function getLayout(page: ReactElement) { +LandingPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; - diff --git a/frontend/src/pages/verify.tsx b/frontend/src/pages/verify.tsx new file mode 100644 index 0000000..4adf5cb --- /dev/null +++ b/frontend/src/pages/verify.tsx @@ -0,0 +1,165 @@ +import React, { useEffect, useState } from 'react'; +import type { ReactElement } from 'react'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import axios from 'axios'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import { mdiShieldCheck, mdiAlertCircle, mdiCheckDecagram, mdiClockOutline, mdiCloseOctagon } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; +import Link from 'next/link'; + +export default function VerificationPage() { + const router = useRouter(); + const { code } = router.query; + const [loading, setLoading] = useState(true); + const [certificate, setCertificate] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (code) { + fetchCertificate(code as string); + } else if (router.isReady && !code) { + setLoading(false); + setError('No se proporcionó un código de verificación.'); + } + }, [code, router.isReady]); + + const fetchCertificate = async (searchCode: string) => { + try { + setLoading(true); + setError(null); + // Search by code + const response = await axios.get(`/certificates?certificate_code=${searchCode}`); + const data = response.data; + + if (data.rows && data.rows.length > 0) { + setCertificate(data.rows[0]); + } else { + setError('Certificado no encontrado. Verifique el código e intente nuevamente.'); + } + } catch (err) { + console.error(err); + setError('Error al conectar con el servidor de validación.'); + } finally { + setLoading(false); + } + }; + + const getStatusInfo = (status: string) => { + switch (status) { + case 'ACTIVO': + return { color: 'text-green-600', bg: 'bg-green-100', icon: mdiCheckDecagram, label: 'VÁLIDO / ACTIVO' }; + case 'VENCIDO': + return { color: 'text-amber-600', bg: 'bg-amber-100', icon: mdiClockOutline, label: 'CADUCADO' }; + case 'REVOCADO': + return { color: 'text-red-600', bg: 'bg-red-100', icon: mdiCloseOctagon, label: 'REVOCADO / ANULADO' }; + default: + return { color: 'text-gray-600', bg: 'bg-gray-100', icon: mdiAlertCircle, label: 'DESCONOCIDO' }; + } + }; + + return ( +
+ + {getPageTitle('Verificación de Certificado')} + + + + +
+
+ {loading ? ( +
+
+

Validando autenticidad en el registro oficial...

+
+ ) : error ? ( +
+
+ +
+

Validación Fallida

+

{error}

+ + Volver al Inicio + +
+ ) : certificate && ( +
+
+
+ +
+

+ {getStatusInfo(certificate.status).label} +

+

Sello de Verificación Digital

+
+ +
+
+
+

Líder Certificado

+

+ {certificate.leader?.firstName} {certificate.leader?.lastName} +

+
+
+

Tipo de Credencial

+

{certificate.title}

+
+
+

Código de Registro

+

+ {certificate.certificate_code} +

+
+
+

Fecha de Emisión

+

+ {new Date(certificate.issue_date).toLocaleDateString()} +

+
+
+ +
+

Descripción de Aval

+

+ "{certificate.description}" +

+
+ +
+ +

Esta certificación ha sido emitida por la Corporación Renacer DDHH Mundiales y su integridad está garantizada mediante firma digital HMAC-SHA256.

+
+
+ +
+ + Nueva Consulta de Verificación + +
+
+ )} +
+
+ +
+

© 2026 Corporación Renacer DDHH Mundiales. NIT: 9018960153.

+
+
+ ); +} + +VerificationPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file