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) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
+ 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 */}
+
+
+
¿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 */}
+
-
-
-
-
© 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
+
+
+
+ )}
+
+
+
+
+
+ );
+}
+
+VerificationPage.getLayout = function getLayout(page: ReactElement) {
+ return {page};
+};
\ No newline at end of file