trabajo preliminar, jueves antes de la reunión
BIN
Documents_ref/Metodología.pdf
Normal file
BIN
Documents_ref/Metodología.pptx
Normal file
BIN
Documents_ref/Oldy.pptx
Normal file
42
Documents_ref/conf.sql
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
-- export forma_db='{
|
||||||
|
-- "host":"127.0.0.1",
|
||||||
|
-- "port":5432,
|
||||||
|
-- "database":"forma",
|
||||||
|
-- "user":"postgres",
|
||||||
|
-- "password":"Shala55951254"
|
||||||
|
-- }';
|
||||||
|
|
||||||
|
-- psql -h 127.0.0.1 -U postgres -a -f conf.sql
|
||||||
|
|
||||||
|
DROP DATABASE IF EXISTS forma;
|
||||||
|
|
||||||
|
CREATE DATABASE forma;
|
||||||
|
|
||||||
|
\c forma;
|
||||||
|
|
||||||
|
CREATE TABLE contact (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
fecha VARCHAR(10),
|
||||||
|
hora VARCHAR(10),
|
||||||
|
nombre VARCHAR(50),
|
||||||
|
apellido VARCHAR(100),
|
||||||
|
email VARCHAR(150),
|
||||||
|
estado VARCHAR(30),
|
||||||
|
num_tel VARCHAR(20),
|
||||||
|
size_co VARCHAR(40),
|
||||||
|
rol_contacto VARCHAR(50),
|
||||||
|
industry_type VARCHAR(40),
|
||||||
|
tipo_req VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users(
|
||||||
|
id VARCHAR(25),
|
||||||
|
nombre VARCHAR(50),
|
||||||
|
apellido VARCHAR(100),
|
||||||
|
email VARCHAR(150),
|
||||||
|
pswd VARCHAR(100),
|
||||||
|
isAdmin boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO users (id, nombre, apellido, email, pswd, isAdmin) VALUES
|
||||||
|
('4HlOjqJ6jLISxNQIbs2Hzz', 'David', 'Itehua Xalamihua', 'davidix1991@gmail.com', '$2b$12$dbJWK5mv89PszxPeXlql5Otd8vv7kz6M44JnKZcrwJdKoovayiqEm', true);
|
24
Documents_ref/mediaquerys.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* Smartphones (hasta 767px) */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
/* body{ background-color: black; } */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablets (768px - 1023px) */
|
||||||
|
@media (min-width: 768px) and (max-width: 1023px) {
|
||||||
|
/* body{ background-color: pink; } */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Laptops (1024px - 1439px) */
|
||||||
|
@media (min-width: 1024px) and (max-width: 1439px) {
|
||||||
|
/* body{ background-color: purple; } */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PCs de escritorio (1440px - 1919px) */
|
||||||
|
@media (min-width: 1440px) and (max-width: 1919px) {
|
||||||
|
/* body{ background-color: greenyellow; } */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pantallas Ultrawide (1920px en adelante) */
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
/* body{ background-color: red; } */
|
||||||
|
}
|
BIN
forms_py/__pycache__/cls_db.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/cls_db_usr.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/cls_form_contact.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/cls_form_login.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/cls_recover_pswd.cpython-312.pyc
Normal file
BIN
forms_py/__pycache__/functions.cpython-312.pyc
Normal file
44
forms_py/cls_db.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import psycopg2
|
||||||
|
from psycopg2 import sql
|
||||||
|
from psycopg2.extras import execute_values
|
||||||
|
|
||||||
|
class DBContact:
|
||||||
|
def __init__(self, db_obj: dict):
|
||||||
|
"""
|
||||||
|
Inicializa la conexión a la base de datos.
|
||||||
|
|
||||||
|
:param db_obj: Diccionario con las credenciales de la base de datos.
|
||||||
|
"""
|
||||||
|
self.db_obj = db_obj
|
||||||
|
|
||||||
|
def _get_connection(self):
|
||||||
|
"""
|
||||||
|
Crea y retorna una nueva conexión a la base de datos.
|
||||||
|
"""
|
||||||
|
return psycopg2.connect(
|
||||||
|
host=self.db_obj['host'],
|
||||||
|
port=self.db_obj['port'],
|
||||||
|
database=self.db_obj['database'],
|
||||||
|
user=self.db_obj['user'],
|
||||||
|
password=self.db_obj['password']
|
||||||
|
)
|
||||||
|
|
||||||
|
def carga_contact(self, data_tuple: tuple):
|
||||||
|
"""
|
||||||
|
Inserta un nuevo contacto en la base de datos.
|
||||||
|
|
||||||
|
:param data_tuple: Tupla con los valores (nombre, apellido, email, num_tel, size_co, rol_contacto, industry_type, tipo_req).
|
||||||
|
:raises Exception: Si ocurre un error durante la ejecución de la consulta.
|
||||||
|
"""
|
||||||
|
query = sql.SQL("""
|
||||||
|
INSERT INTO contact (fecha, hora, nombre, apellido, email, num_tel, size_co, rol_contacto, industry_type, tipo_req)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(query, data_tuple)
|
||||||
|
conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Error al insertar contacto: {e}")
|
95
forms_py/cls_db_usr.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import psycopg2
|
||||||
|
from psycopg2 import sql
|
||||||
|
from psycopg2.extras import execute_values
|
||||||
|
|
||||||
|
class DBForma:
|
||||||
|
def __init__(self, db_obj: dict):
|
||||||
|
"""
|
||||||
|
Inicializa la conexión a la base de datos.
|
||||||
|
|
||||||
|
:param db_obj: Diccionario con las credenciales de la base de datos.
|
||||||
|
Debe contener: host, port, database, user, password
|
||||||
|
"""
|
||||||
|
self.db_obj = db_obj
|
||||||
|
|
||||||
|
def _get_connection(self):
|
||||||
|
"""
|
||||||
|
Crea y retorna una nueva conexión a la base de datos.
|
||||||
|
"""
|
||||||
|
return psycopg2.connect(
|
||||||
|
host=self.db_obj['host'],
|
||||||
|
port=self.db_obj['port'],
|
||||||
|
database=self.db_obj['database'],
|
||||||
|
user=self.db_obj['user'],
|
||||||
|
password=self.db_obj['password']
|
||||||
|
)
|
||||||
|
|
||||||
|
def login(self, email: str):
|
||||||
|
"""
|
||||||
|
Verifica las credenciales de un usuario.
|
||||||
|
|
||||||
|
:param email: Email del usuario a verificar
|
||||||
|
:return: Tupla con la contraseña si el usuario existe, None si no existe
|
||||||
|
:raises: RuntimeError si hay algún error en la consulta
|
||||||
|
"""
|
||||||
|
# Corrección 1: Usar parámetros correctamente para evitar SQL injection
|
||||||
|
query = "SELECT pswd FROM users WHERE email = %s;"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
# Corrección 2: Pasar parámetros como tupla (aunque sea uno solo)
|
||||||
|
cursor.execute(query, (email,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Error al verificar credenciales: {e}")
|
||||||
|
|
||||||
|
def reset_pswd(self, email: str):
|
||||||
|
query = "SELECT email FROM users WHERE email = %s;"
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(query, (email,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Error al verificar credenciales: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_id(self, email: str):
|
||||||
|
query = "SELECT id FROM users WHERE email = %s;"
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(query, (email,))
|
||||||
|
return cursor.fetchone()
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Error al verificar credenciales: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def update_pswd(self, pswd: str, email: str) -> bool:
|
||||||
|
"""
|
||||||
|
Actualiza la contraseña de un usuario en la base de datos.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pswd: Nueva contraseña (debería estar hasheada)
|
||||||
|
email: Email del usuario a actualizar
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True si la actualización fue exitosa, False si no se actualizó ningún registro
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: Si ocurre un error durante la operación
|
||||||
|
"""
|
||||||
|
query = "UPDATE users SET pswd = %s WHERE email = %s;"
|
||||||
|
try:
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(query, (pswd, email))
|
||||||
|
# Verificar si realmente se actualizó algún registro
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
return False
|
||||||
|
conn.commit() # Confirmar explícitamente la transacción
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback() # Revertir en caso de error
|
||||||
|
raise RuntimeError(f"Error al actualizar la contraseña: {e}")
|
124
forms_py/cls_form_contact.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, TextAreaField, SelectField
|
||||||
|
from wtforms.validators import InputRequired, Email, Length, Optional, DataRequired
|
||||||
|
|
||||||
|
class ContactForm(FlaskForm):
|
||||||
|
nombre = StringField(
|
||||||
|
"Nombre", validators=[InputRequired(message="Este campo es obligatorio"), Length(max=100, message="Máximo 100 caracteres")]
|
||||||
|
)
|
||||||
|
|
||||||
|
apellido = StringField(
|
||||||
|
"Apellido", validators=[InputRequired(message="Este campo es obligatorio"), Length(max=100, message="Máximo 100 caracteres")]
|
||||||
|
)
|
||||||
|
|
||||||
|
email = StringField(
|
||||||
|
"Email", validators=[InputRequired(message="Este campo es obligatorio"), Email(message="Ingresa un email válido"), Length(max=120)]
|
||||||
|
)
|
||||||
|
|
||||||
|
estado = SelectField("Estado", choices=[
|
||||||
|
("", ""),
|
||||||
|
(1, 'Aguascalientes'),
|
||||||
|
(2, 'Baja California'),
|
||||||
|
(3, 'Baja California Sur'),
|
||||||
|
(4, 'Campeche'),
|
||||||
|
(5, 'Chiapas'),
|
||||||
|
(6, 'Chihuahua'),
|
||||||
|
(7, 'Ciudad de México'),
|
||||||
|
(8, 'Coahuila'),
|
||||||
|
(9, 'Colima'),
|
||||||
|
(10, 'Durango'),
|
||||||
|
(11, 'Estado de México'),
|
||||||
|
(12, 'Guanajuato'),
|
||||||
|
(13, 'Guerrero'),
|
||||||
|
(14, 'Hidalgo'),
|
||||||
|
(15, 'Jalisco'),
|
||||||
|
(16, 'Michoacán'),
|
||||||
|
(17, 'Morelos'),
|
||||||
|
(18, 'Nayarit'),
|
||||||
|
(19, 'Nuevo León'),
|
||||||
|
(20, 'Oaxaca'),
|
||||||
|
(21, 'Puebla'),
|
||||||
|
(22, 'Querétaro'),
|
||||||
|
(23, 'Quintana Roo'),
|
||||||
|
(24, 'San Luis Potosí'),
|
||||||
|
(25, 'Sinaloa'),
|
||||||
|
(26, 'Sonora'),
|
||||||
|
(27, 'Tabasco'),
|
||||||
|
(28, 'Tamaulipas'),
|
||||||
|
(29, 'Tlaxcala'),
|
||||||
|
(30, 'Veracruz'),
|
||||||
|
(31, 'Yucatán'),
|
||||||
|
(32, 'Zacatecas')
|
||||||
|
], validators=[DataRequired(message="Debes seleccionar un estado.")],)
|
||||||
|
|
||||||
|
|
||||||
|
num_tel = StringField(
|
||||||
|
"Número telefónico", validators=[InputRequired(message="Número requerido"), Length(min=8, max=20, message="Número inválido")]
|
||||||
|
)
|
||||||
|
|
||||||
|
size_co = SelectField( "Tamaño empresa", choices=[
|
||||||
|
("", ""),
|
||||||
|
("1-10", "1 - 20"),
|
||||||
|
("10-50", "21 - 50"),
|
||||||
|
("50-100", "51 - 100"),
|
||||||
|
("101-149", "101 - 149"),
|
||||||
|
("150-500", "150 - 500"),
|
||||||
|
("501-799", "501 - 799"),
|
||||||
|
("800-5,000", "800 - 5,000"),
|
||||||
|
("5,000-10,000", "5,001 - 10,000"),
|
||||||
|
("10,000+", "10,000+")
|
||||||
|
], validators=[DataRequired(message="Debes seleccionar el tamaño de la empresa.")],)
|
||||||
|
|
||||||
|
|
||||||
|
rol_contacto = SelectField( "Rol desempeñado", choices=[
|
||||||
|
("", ""),
|
||||||
|
(1, 'Capacitaciones'),
|
||||||
|
(2, 'Gerente de Operaciones'),
|
||||||
|
(3, 'Gerente de TI'),
|
||||||
|
(4, 'Nómina'),
|
||||||
|
(5, 'Desarrollo Organizacional'),
|
||||||
|
(6, 'Reclutamiento y selección'),
|
||||||
|
(7, 'Gerencia de Recursos Humanos'),
|
||||||
|
(8, 'Finanzas / Gerencia'),
|
||||||
|
(9, 'Dueño de mi propio negocio'),
|
||||||
|
(10, 'Estudiante'),
|
||||||
|
(11, 'Otro')
|
||||||
|
], validators=[DataRequired(message="Debes selecionar tu rol.")],)
|
||||||
|
|
||||||
|
industry_type = SelectField( "Sector", choices=[
|
||||||
|
("", ""),
|
||||||
|
(1, 'Agricola'),
|
||||||
|
(2, 'Alimentos'),
|
||||||
|
(3, 'Automotriz'),
|
||||||
|
(4, 'Comercio'),
|
||||||
|
(5, 'Comunicaciones'),
|
||||||
|
(6, 'Construcción'),
|
||||||
|
(7, 'Consultora'),
|
||||||
|
(8, 'Educación'),
|
||||||
|
(9, 'Empresas B'),
|
||||||
|
(10, 'Energia'),
|
||||||
|
(11, 'Entretenimiento'),
|
||||||
|
(12, 'Financiera'),
|
||||||
|
(13, 'Fundación'),
|
||||||
|
(14, 'Holding'),
|
||||||
|
(15, 'Hoteleria'),
|
||||||
|
(16, 'Legal'),
|
||||||
|
(17, 'Logistica'),
|
||||||
|
(18, 'Manufacturera'),
|
||||||
|
(19, 'Marketing'),
|
||||||
|
(20, 'Minera'),
|
||||||
|
(21, 'Otra'),
|
||||||
|
(22, 'Sector Público'),
|
||||||
|
(23, 'Restoranes/Cafeteria'),
|
||||||
|
(24, 'RRHH'),
|
||||||
|
(25, 'Salud'),
|
||||||
|
(26, 'Seguridad'),
|
||||||
|
(27, 'Servicios'),
|
||||||
|
(28, 'Tecnología'),
|
||||||
|
(29, 'Transporte'),
|
||||||
|
(30, 'Utilities (gas, agua, electricidad)')
|
||||||
|
], validators=[DataRequired(message="Selecione una propiedad.")],)
|
||||||
|
|
||||||
|
tipo_req = TextAreaField(
|
||||||
|
"Objetivo del contacto", validators=[InputRequired(message="Describe brevemente tu necesidad"), Length(min=10, max=500, message="Mínimo 10 caracteres, máximo 500")]
|
||||||
|
)
|
35
forms_py/cls_form_login.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, PasswordField
|
||||||
|
from wtforms.validators import InputRequired, Email, Length, DataRequired
|
||||||
|
|
||||||
|
class LogIn(FlaskForm):
|
||||||
|
"""
|
||||||
|
Formulario de inicio de sesión que hereda de FlaskForm.
|
||||||
|
|
||||||
|
Campos:
|
||||||
|
email (StringField): Campo para el correo electrónico del usuario.
|
||||||
|
password (PasswordField): Campo para la contraseña del usuario.
|
||||||
|
|
||||||
|
Validaciones:
|
||||||
|
- Email: Requerido, formato válido y longitud máxima de 120 caracteres.
|
||||||
|
- Password: Requerido.
|
||||||
|
"""
|
||||||
|
|
||||||
|
email = StringField(
|
||||||
|
"Email",
|
||||||
|
validators=[
|
||||||
|
InputRequired(message="Este campo es obligatorio"),
|
||||||
|
Email(message="Ingresa un email válido"),
|
||||||
|
Length(max=120, message="El email no puede exceder los 120 caracteres")
|
||||||
|
],
|
||||||
|
description="Correo electrónico registrado en la plataforma"
|
||||||
|
)
|
||||||
|
|
||||||
|
password = PasswordField(
|
||||||
|
'Contraseña',
|
||||||
|
validators=[
|
||||||
|
DataRequired(message="La contraseña es obligatoria")
|
||||||
|
],
|
||||||
|
description="Contraseña de acceso a la cuenta",
|
||||||
|
render_kw={"placeholder": "Ingresa tu contraseña"}
|
||||||
|
)
|
33
forms_py/cls_recover_pswd.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, validators
|
||||||
|
from wtforms.validators import DataRequired, Email, Length
|
||||||
|
|
||||||
|
class RecoverPswd(FlaskForm):
|
||||||
|
"""
|
||||||
|
Formulario para recuperación de contraseña.
|
||||||
|
|
||||||
|
Campos:
|
||||||
|
email (StringField): Campo para el correo electrónico del usuario con validaciones.
|
||||||
|
|
||||||
|
Validaciones:
|
||||||
|
- Requerido (DataRequired)
|
||||||
|
- Formato válido de email (Email)
|
||||||
|
- Longitud máxima de 120 caracteres (Length)
|
||||||
|
- Sanitización automática (elimina espacios)
|
||||||
|
"""
|
||||||
|
|
||||||
|
email = StringField(
|
||||||
|
label="Correo Electrónico",
|
||||||
|
description="Ingresa el email asociado a tu cuenta",
|
||||||
|
filters=[lambda x: x.strip() if x else x], # Sanitización
|
||||||
|
validators=[
|
||||||
|
DataRequired(message="El correo electrónico es requerido"),
|
||||||
|
Email(message="Por favor ingresa un email válido"),
|
||||||
|
Length(max=120, message="El email no puede exceder los 120 caracteres")
|
||||||
|
],
|
||||||
|
render_kw={
|
||||||
|
"placeholder": "ejemplo@correo.com",
|
||||||
|
"class": "form-control", # Para Bootstrap
|
||||||
|
"autocomplete": "email"
|
||||||
|
}
|
||||||
|
)
|
96
forms_py/functions.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
from email.message import EmailMessage
|
||||||
|
import smtplib
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import string
|
||||||
|
import secrets
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
import shortuuid
|
||||||
|
|
||||||
|
|
||||||
|
bcrypt = Bcrypt()
|
||||||
|
|
||||||
|
|
||||||
|
v = {
|
||||||
|
'home': 'home/home.html',
|
||||||
|
'about-us': 'about-us/about-us.html',
|
||||||
|
'solutions': 'solutions/solutions.html',
|
||||||
|
'methodology': 'methodology/methodology.html',
|
||||||
|
'contact': 'contact/contact.html',
|
||||||
|
'login': 'login/login.html',
|
||||||
|
'usr_home': 'usr_home/usr_home.html',
|
||||||
|
'recover_pswd': 'login/recover_pswd.html'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def db_conf_obj(dbEnvVarName: str) -> object:
|
||||||
|
'''
|
||||||
|
dbEnvVarName = nombre de la variable de entorno
|
||||||
|
variable que contiene los datos de la base de datos
|
||||||
|
'''
|
||||||
|
db = os.getenv(dbEnvVarName)
|
||||||
|
db = json.loads(db)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def generar_contrasena():
|
||||||
|
"""
|
||||||
|
Genera una contraseña aleatoria de 25 caracteres.
|
||||||
|
|
||||||
|
La contraseña se compone de una combinación de letras mayúsculas, letras minúsculas y dígitos. La función utiliza
|
||||||
|
el módulo `secrets` para asegurar que la generación de la contraseña sea adecuada para propósitos de seguridad.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Una cadena de caracteres aleatorios de 25 caracteres, formada por letras (mayúsculas y minúsculas) y dígitos.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
--------
|
||||||
|
>>> generar_contrasena()
|
||||||
|
'A9gTz5bNp0Wk3L6Xq2vUv8YwRz1E'
|
||||||
|
"""
|
||||||
|
caracteres = string.ascii_letters + string.digits
|
||||||
|
contrasena = ''.join(secrets.choice(caracteres) for _ in range(25))
|
||||||
|
return contrasena
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
"""
|
||||||
|
Genera un hash de la contraseña utilizando bcrypt.
|
||||||
|
|
||||||
|
Esta función toma una contraseña en texto plano y la convierte en un hash seguro utilizando el algoritmo bcrypt.
|
||||||
|
El hash generado es una cadena en formato UTF-8, adecuada para almacenamiento en bases de datos.
|
||||||
|
|
||||||
|
Parámetros:
|
||||||
|
----------
|
||||||
|
password : str
|
||||||
|
La contraseña en texto plano que se desea hashear.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
-------
|
||||||
|
str or None
|
||||||
|
El hash de la contraseña en formato UTF-8 si la operación es exitosa.
|
||||||
|
Devuelve `None` en caso de que ocurra una excepción durante el proceso de hashing.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
--------
|
||||||
|
>>> hash_password('mi_contrasena_segura')
|
||||||
|
'$2b$12$D4yU/jEaK4xdgK0R2J6c6Odnk8p3k/RtG2ByjF26.4gnBR4tdA/2i'
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||||
|
return hashed_password
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getRandomId():
|
||||||
|
# POR DEFAULT SHORUUID SACA 22 CARACTERES
|
||||||
|
# LOS PODEMOS ACORTAR DE LA SIGUIENTE MANERA: short_id_custom = shortuuid.uuid()[:10]
|
||||||
|
# Configurar el generador con una semilla específica (opcional)
|
||||||
|
# shortuuid.set_alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-!#$!%&/()=?¡¿")
|
||||||
|
shortuuid.set_alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
# Generar un UUID corto con la semilla personalizada
|
||||||
|
short_id_custom = shortuuid.uuid()
|
||||||
|
return short_id_custom
|
||||||
|
|
||||||
|
|
||||||
|
|
230
main.py
@ -0,0 +1,230 @@
|
|||||||
|
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity, get_jwt
|
||||||
|
from flask_mail import Message, Mail
|
||||||
|
from threading import Thread
|
||||||
|
from flask import Flask, render_template, redirect, url_for, flash, current_app # Añadí current_app
|
||||||
|
from flask import jsonify, make_response # Añade estas importaciones
|
||||||
|
from forms_py.cls_form_contact import ContactForm
|
||||||
|
from forms_py.cls_form_login import LogIn
|
||||||
|
from forms_py.cls_db import DBContact
|
||||||
|
from forms_py.cls_db_usr import DBForma
|
||||||
|
from forms_py.functions import db_conf_obj, generar_contrasena, hash_password, v
|
||||||
|
from forms_py.cls_recover_pswd import RecoverPswd
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
|
||||||
|
|
||||||
|
email_sender = os.getenv("email_sender")
|
||||||
|
email_pswd = os.getenv("pswd_formha")
|
||||||
|
lst_email_to = ["davidix1991@gmail.com", "davicho1991@live.com"]
|
||||||
|
|
||||||
|
# Configuración de JWT (añade esto junto a tus otras configs)
|
||||||
|
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', 'super-secret-fallback-key') # Usa una clave segura en producción -> MOVER A VARIABLE DE ENTORNO
|
||||||
|
# app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1) # Token expira en 1 hora
|
||||||
|
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=30) # Token expira en 1 hora
|
||||||
|
app.config['JWT_COOKIE_SECURE'] = True # En producción debe ser True
|
||||||
|
app.config['JWT_COOKIE_CSRF_PROTECT'] = True # Recomendado para seguridad
|
||||||
|
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
|
||||||
|
# FLASK EMAIL
|
||||||
|
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
||||||
|
app.config['MAIL_PORT'] = 465
|
||||||
|
app.config['MAIL_USE_SSL'] = True
|
||||||
|
app.config['MAIL_USERNAME'] = email_sender # email en variable de entorno
|
||||||
|
app.config['MAIL_PASSWORD'] = email_pswd # contraseña en variable de entorno
|
||||||
|
app.config['SECRET_KEY'] = 'FoRmHä$2025' # Necesario para CSRF y mensajes flash -> la debo colocar en variable de entono?
|
||||||
|
|
||||||
|
|
||||||
|
mail = Mail(app)
|
||||||
|
|
||||||
|
jwt = JWTManager(app)
|
||||||
|
|
||||||
|
jsonDbContact = db_conf_obj("forma_db")
|
||||||
|
dbContact = DBContact(jsonDbContact)
|
||||||
|
dbUsers = DBForma(jsonDbContact)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def send_async_email(app, msg):
|
||||||
|
with app.app_context():
|
||||||
|
mail.send(msg)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def home():
|
||||||
|
return render_template(v['home'], active_page='home')
|
||||||
|
|
||||||
|
@app.route('/about-us')
|
||||||
|
def about_us():
|
||||||
|
return render_template(v['about-us'], active_page='about_us')
|
||||||
|
|
||||||
|
@app.route('/solutions')
|
||||||
|
def solutions():
|
||||||
|
return render_template(v['solutions'], active_page='solutions')
|
||||||
|
|
||||||
|
@app.route('/methodology')
|
||||||
|
def methodology():
|
||||||
|
return render_template(v['methodology'], active_page='methodology')
|
||||||
|
|
||||||
|
@app.route("/contact", methods=['GET', 'POST'])
|
||||||
|
def contact():
|
||||||
|
form = ContactForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit(): # Corregí "validate_on_sumbit" a "validate_on_submit"
|
||||||
|
cur_date = datetime.now().strftime("%d/%m/%Y")
|
||||||
|
cur_hour = datetime.now().strftime("%H:%M")
|
||||||
|
|
||||||
|
# Procesar datos del formulario
|
||||||
|
flash('¡Gracias por contactarnos! Te responderemos pronto.', 'success')
|
||||||
|
data = (cur_date, cur_hour, form.nombre.data, form.apellido.data,
|
||||||
|
form.email.data, form.num_tel.data, form.size_co.data,
|
||||||
|
form.rol_contacto.data, form.industry_type.data, form.tipo_req.data)
|
||||||
|
dbContact.carga_contact(data)
|
||||||
|
|
||||||
|
# Configurar y enviar email asíncrono
|
||||||
|
msg = Message(
|
||||||
|
"Subject, una persona busca asesoria", sender=email_sender, recipients=lst_email_to
|
||||||
|
)
|
||||||
|
|
||||||
|
msg.html = """
|
||||||
|
<h1>Nuevo contacto recibido</h1>
|
||||||
|
<p><strong>Nombre:</strong> {} {}</p>
|
||||||
|
<p><strong>Email:</strong> {}</p>
|
||||||
|
<p><strong>Teléfono:</strong> {}</p>
|
||||||
|
<p><strong>Mensaje:</strong> {}</p>
|
||||||
|
""".format(form.nombre.data, form.apellido.data, form.email.data,
|
||||||
|
form.num_tel.data, form.tipo_req.data)
|
||||||
|
|
||||||
|
# Enviar en segundo plano
|
||||||
|
thr = Thread(
|
||||||
|
target=send_async_email,
|
||||||
|
args=(current_app._get_current_object(), msg)
|
||||||
|
)
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
return redirect(url_for('contact'))
|
||||||
|
|
||||||
|
return render_template(v['contact'], form=form, active_page='contact')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login", methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
form = LogIn()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
f_email = f"{form.email.data}".lower()
|
||||||
|
f_pswd = form.password.data
|
||||||
|
res_pswd_server = dbUsers.login((f_email))
|
||||||
|
|
||||||
|
if res_pswd_server is None:
|
||||||
|
flash('Usuario no registrado en la db', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
res_pswd_server = res_pswd_server[0]
|
||||||
|
if bcrypt.check_password_hash(res_pswd_server, f_pswd):
|
||||||
|
|
||||||
|
id_user = dbUsers.get_id(f_email)[0]
|
||||||
|
|
||||||
|
# Crear token JWT
|
||||||
|
access_token = create_access_token(identity=id_user)
|
||||||
|
|
||||||
|
# Redirigir a usr_home
|
||||||
|
response = make_response(redirect(url_for('usr_home')))
|
||||||
|
response.set_cookie('access_token_cookie', access_token, httponly=True, secure=True, samesite='Lax' )
|
||||||
|
flash('Inicio de sesión exitoso', 'success')
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
flash('Credenciales incorrectas', 'error')
|
||||||
|
|
||||||
|
return render_template(v['login'], form=form, active_page='login')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/recover-pswd", methods=['GET', 'POST'])
|
||||||
|
def recover_pswd():
|
||||||
|
form = RecoverPswd()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
f_email = f"{form.email.data}".lower()
|
||||||
|
emailPswdReco = dbUsers.reset_pswd(f_email)
|
||||||
|
|
||||||
|
if emailPswdReco is None:
|
||||||
|
flash('Email no válido', 'error')
|
||||||
|
return render_template(v['recover_pswd'], form=form, active_page='login')
|
||||||
|
|
||||||
|
emailPswdReco = emailPswdReco[0]
|
||||||
|
new_tmp_pswd = generar_contrasena()
|
||||||
|
hashed_new_pswd = hash_password(new_tmp_pswd)
|
||||||
|
|
||||||
|
# Configurar y enviar email asíncrono
|
||||||
|
msg = Message(
|
||||||
|
"Subject: Recuperación de contraseña",
|
||||||
|
sender=email_sender,
|
||||||
|
recipients=[emailPswdReco] # Asegúrate que es una lista
|
||||||
|
)
|
||||||
|
|
||||||
|
# msg.html = render_template( 'email/recover_pswd.html', temp_password=new_tmp_pswd )
|
||||||
|
|
||||||
|
msg.html = """
|
||||||
|
<h1>Nueva contraseña temporal:</h1>
|
||||||
|
<p><strong>Contraseña:</strong> {}</p>
|
||||||
|
<p><strong>Una vez iniciada tu sesión debes de cambiar la contraseña a una nueva que puedas recordar</strong></p>
|
||||||
|
""".format(new_tmp_pswd)
|
||||||
|
|
||||||
|
dbUsers.update_pswd(pswd=hashed_new_pswd, email=emailPswdReco)
|
||||||
|
|
||||||
|
# Enviar en segundo plano
|
||||||
|
thr = Thread(
|
||||||
|
target=send_async_email,
|
||||||
|
args=(current_app._get_current_object(), msg)
|
||||||
|
)
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
flash("Se ha enviado una contraseña temporal a tu correo electrónico", "success")
|
||||||
|
return redirect(url_for('login')) # Redirige en lugar de renderizar
|
||||||
|
|
||||||
|
return render_template(v['recover_pswd'], form=form, active_page='login')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/user/home')
|
||||||
|
@jwt_required() # Protege esta ruta
|
||||||
|
def usr_home():
|
||||||
|
current_user = get_jwt_identity() # Obtiene el identity (normalmente el email)
|
||||||
|
token_data = get_jwt() # Obtiene TODOS los datos del token decodificado
|
||||||
|
|
||||||
|
# print("Token completo:", token_data)
|
||||||
|
# print("Usuario:", current_user)
|
||||||
|
|
||||||
|
|
||||||
|
return render_template(v['usr_home'], current_user=current_user, token_data=token_data)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
# Manejo de errores JWT
|
||||||
|
@jwt.expired_token_loader
|
||||||
|
def handle_expired_token(jwt_header, jwt_payload):
|
||||||
|
flash('Tu sesión ha expirado. Por favor inicia sesión nuevamente', 'warning')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@jwt.unauthorized_loader
|
||||||
|
def handle_unauthorized_error(reason):
|
||||||
|
flash(f'Debes iniciar sesión para acceder a esta página: {reason}', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@jwt.invalid_token_loader
|
||||||
|
def handle_invalid_token_error(reason):
|
||||||
|
flash(f'Sesión inválida: {reason}', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
response = make_response(redirect(url_for('login')))
|
||||||
|
response.delete_cookie('access_token_cookie')
|
||||||
|
flash('Sesión cerrada correctamente', 'success')
|
||||||
|
return response
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=8089)
|
20
requirements.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
bcrypt==4.3.0
|
||||||
|
blinker==1.9.0
|
||||||
|
click==8.1.8
|
||||||
|
dnspython==2.7.0
|
||||||
|
email_validator==2.2.0
|
||||||
|
Flask==3.1.0
|
||||||
|
Flask-Bcrypt==1.0.1
|
||||||
|
Flask-JWT-Extended==4.7.1
|
||||||
|
Flask-Mail==0.10.0
|
||||||
|
Flask-WTF==1.2.2
|
||||||
|
idna==3.10
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.6
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
psycopg2-binary==2.9.10
|
||||||
|
PyJWT==2.10.1
|
||||||
|
python-dotenv==1.1.0
|
||||||
|
shortuuid==1.0.13
|
||||||
|
Werkzeug==3.1.3
|
||||||
|
WTForms==3.2.1
|
210
static/about-us/about-us.css
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* EXPANDING PANELS - Componente de paneles expandibles
|
||||||
|
*
|
||||||
|
* Estilos para un sistema de paneles con:
|
||||||
|
* - Efecto hover con animación
|
||||||
|
* - Texto expandible con scroll
|
||||||
|
* - Diseño responsive
|
||||||
|
*/
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
display: grid;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* ESTILOS BASE DEL PANEL */
|
||||||
|
/* ---------------------------- */
|
||||||
|
.panel {
|
||||||
|
position: relative;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* CONTENIDO DEL PANEL */
|
||||||
|
/* ---------------------------- */
|
||||||
|
.panel-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #000;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Título del panel - siempre visible */
|
||||||
|
.panel-title {
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Texto expandible - con scroll vertical cuando es necesario */
|
||||||
|
.panel-text {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
overflow-y: hidden; /* Oculta scroll inicialmente */
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
margin-top: 1em;
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
padding: 0 1em;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
& p, ul {
|
||||||
|
text-align: justify;
|
||||||
|
text-justify: inter-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* EFECTOS HOVER */
|
||||||
|
/* ---------------------------- */
|
||||||
|
.panel:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.panel-text {
|
||||||
|
max-height: 50vh; /* Altura máxima antes de mostrar scroll */
|
||||||
|
opacity: 1;
|
||||||
|
padding: 1em;
|
||||||
|
overflow-y: auto; /* Muestra scroll solo cuando es necesario */
|
||||||
|
|
||||||
|
/* Estilos personalizados para la barra de scroll */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* IMÁGENES DE FONDO */
|
||||||
|
/* ---------------------------- */
|
||||||
|
.p1 { background-image: url('/static/img/about-us/team.avif'); }
|
||||||
|
.p2 { background-image: url('/static/img/about-us/mision.avif'); }
|
||||||
|
.p3 { background-image: url('/static/img/about-us/idea.avif'); }
|
||||||
|
.p4 { background-image: url('/static/img/about-us/mex.avif'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================ */
|
||||||
|
/* MEDIA QUERIES - RESPONSIVE DESIGN */
|
||||||
|
/* ============================================ */
|
||||||
|
|
||||||
|
/* Smartphones (hasta 767px) */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
|
||||||
|
main{
|
||||||
|
width: 95vw;
|
||||||
|
margin: 1em auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
grid-template-rows: repeat(4, 1fr);
|
||||||
|
row-gap: 2em;
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
height: 45vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablets (768px - 1023px) */
|
||||||
|
@media (min-width: 768px) and (max-width: 1023px) {
|
||||||
|
|
||||||
|
main{
|
||||||
|
width: 90vw;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
height: 45vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Laptops (1024px - 1439px) */
|
||||||
|
@media (min-width: 1024px) and (max-width: 1439px) {
|
||||||
|
|
||||||
|
main{
|
||||||
|
width: 90vw;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
height: 40vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PCs de escritorio (1440px - 1919px) */
|
||||||
|
@media (min-width: 1440px) and (max-width: 1919px) {
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
height: 75vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main{
|
||||||
|
width: 90vw;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pantallas Ultrawide (1920px en adelante) */
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
|
||||||
|
.expanding-panels {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
height: 75vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main{
|
||||||
|
width: 80vw;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
140
static/home/home.css
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/* Variables para consistencia */
|
||||||
|
:root {
|
||||||
|
--primary-color: #00acc1;
|
||||||
|
--shadow-light: 0 3px 4px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-hover: 0 15px 26px rgba(0, 0, 0, 0.5);
|
||||||
|
--card-radius: 5px;
|
||||||
|
--image-height: 250px;
|
||||||
|
--banner-text-color: #ffff;
|
||||||
|
--banner-overlay: rgba(116, 118, 119, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estructura principal */
|
||||||
|
/* main {
|
||||||
|
width: 95vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* Fila 1 - Banner con efecto de zoom */
|
||||||
|
.r-i {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-i-ii {
|
||||||
|
background-image: url('/static/img/home/reunion.avif');
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-size 2s ease-in-out;
|
||||||
|
height: 35vh; /* Valor por defecto para móviles */
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-i-ii:hover {
|
||||||
|
background-size: 110%; /* Efecto de zoom mantenido */
|
||||||
|
}
|
||||||
|
|
||||||
|
.r-i-ii spam {
|
||||||
|
background-color: var(--banner-overlay);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: clamp(1rem, 3vw, 25px); /* Texto responsivo */
|
||||||
|
color: var(--banner-text-color);
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* Fila 2 - Tarjetas con efectos */
|
||||||
|
.r-ii {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-flyer {
|
||||||
|
min-height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
box-shadow: var(--shadow-light);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-box img {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--image-height);
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.9s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container h6 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Efectos hover (completos) */
|
||||||
|
.card-flyer:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
box-shadow: var(--shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-flyer:hover img {
|
||||||
|
transform: scale(1.15); /* Efecto de escala mantenido */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* Media Queries optimizadas */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
main {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
.r-i-ii {
|
||||||
|
height: 35vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
main {
|
||||||
|
width: 85vw;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
.r-i-ii {
|
||||||
|
height: 45vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
main {
|
||||||
|
width: 80vw;
|
||||||
|
margin: 2em auto;
|
||||||
|
}
|
||||||
|
.r-i-ii {
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
.r-i-ii {
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajuste específico para texto en móviles */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.r-i-ii spam {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
BIN
static/img/about-us/idea.avif
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
static/img/about-us/mex.avif
Normal file
After Width: | Height: | Size: 773 KiB |
BIN
static/img/about-us/mision.avif
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
static/img/about-us/team.avif
Normal file
After Width: | Height: | Size: 577 KiB |
BIN
static/img/home/chair.avif
Normal file
After Width: | Height: | Size: 880 KiB |
BIN
static/img/home/reunion.avif
Normal file
After Width: | Height: | Size: 463 KiB |
BIN
static/img/home/software.avif
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
static/img/home/work.avif
Normal file
After Width: | Height: | Size: 446 KiB |
BIN
static/img/solutions/Imagen1.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
static/img/solutions/Imagen2.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
static/img/solutions/Imagen3.png
Normal file
After Width: | Height: | Size: 191 KiB |
BIN
static/img/solutions/Imagen4.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
static/img/solutions/Imagen5.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
static/img/solutions/Imagen6.png
Normal file
After Width: | Height: | Size: 77 KiB |
0
static/methodology/methodology.css
Normal file
185
static/solutions/solutions.css
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
main {
|
||||||
|
background-image: url('/static/img/solutions/Imagen1.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center bottom;
|
||||||
|
background-color: #50164A;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
padding: 5em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-rows: repeat(3, 1fr);
|
||||||
|
gap: 2em;
|
||||||
|
/* width: 75vw; */
|
||||||
|
/* Altura para que la grilla sea visible */
|
||||||
|
/* height: 80vh; */
|
||||||
|
& div {
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.parent div {
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
animation: vibrate 0.4s ease infinite;
|
||||||
|
transform: scale(1.03);
|
||||||
|
box-shadow: 0 5px 15px rgba(255, 255, 255, 0.4);
|
||||||
|
border-color: #00acc1;
|
||||||
|
filter: brightness(1.05);
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Imágenes específicas */
|
||||||
|
.i-i {
|
||||||
|
background-image: url('/static/img/solutions/Imagen2.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.i-ii {
|
||||||
|
background-image: url('/static/img/solutions/Imagen3.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.i-iii {
|
||||||
|
background-image: url('/static/img/solutions/Imagen4.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.ii-i {
|
||||||
|
background-image: url('/static/img/solutions/Imagen5.png');
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ii-ii {
|
||||||
|
background-image: url('/static/img/solutions/Imagen6.png');
|
||||||
|
grid-column: 3;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Smartphones (hasta 767px) */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 95vw;
|
||||||
|
min-height: 70vh;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-size: auto 15%;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1em; /* Añadido para espacio interno */
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
width: 100vw; /* Cambiado a 100% para mejor ajuste */
|
||||||
|
padding: 1em; /* Reducido el padding para móviles */
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr; /* Solo una columna en móviles */
|
||||||
|
grid-template-rows: repeat(6, minmax(120px, 1fr)); /* Altura mínima garantizada */
|
||||||
|
gap: 1em; /* Espacio reducido para móviles */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Asegurar que todos los divs sean visibles */
|
||||||
|
.parent div {
|
||||||
|
min-height: 120px; /* Altura mínima garantizada */
|
||||||
|
width: 100% !important;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.5); /* Borde más visible */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posicionamiento específico para móviles */
|
||||||
|
.ii-i {
|
||||||
|
grid-row: 4; /* Posición ajustada */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ii-ii {
|
||||||
|
grid-row: 5; /* Posición ajustada */
|
||||||
|
grid-column: 1; /* Reset para móviles */
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablets (768px - 1023px) */
|
||||||
|
@media (min-width: 768px) and (max-width: 1023px) {
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 90vw;
|
||||||
|
min-height: 70vh;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-size: auto 35%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
width: 90vw;
|
||||||
|
/* Altura para que la grilla sea visible */
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Laptops (1024px - 1439px) */
|
||||||
|
@media (min-width: 1024px) and (max-width: 1439px) {
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 85vw;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-size: auto 50%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
width: 80vw;
|
||||||
|
/* Altura para que la grilla sea visible */
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PCs de escritorio (1440px - 1919px) */
|
||||||
|
@media (min-width: 1440px) and (max-width: 1919px) {
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 80vw;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-size: auto 50%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
width: 70vw;
|
||||||
|
/* Altura para que la grilla sea visible */
|
||||||
|
height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pantallas Ultrawide (1920px en adelante) */
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 80vw;
|
||||||
|
min-height: 85vh;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-size: auto 50%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent {
|
||||||
|
width: 70vw;
|
||||||
|
/* Altura para que la grilla sea visible */
|
||||||
|
height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
static/template/navbar.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
.navbar-custom {
|
||||||
|
background-color: #50164A !important;
|
||||||
|
font-size: 20px;
|
||||||
|
/* min-height: 80px; */
|
||||||
|
|
||||||
|
& .navbar-brand, .nav-link {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .navbar-toggler-icon {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 1%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||||
|
}
|
||||||
|
|
||||||
|
& .navbar-nav .nav-item .nav-link {
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Efecto de hover */
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:before,
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:after {
|
||||||
|
content: "";
|
||||||
|
height: 1px;
|
||||||
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
transition: all .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:before {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:after {
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aplicar el efecto de hover al enlace activo */
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link.active:before,
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link.active:after {
|
||||||
|
width: calc(100% + 0px);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aplicar el efecto de hover al hacer hover */
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:hover:before,
|
||||||
|
& .navbar-nav.effect-3 .nav-item .nav-link:hover:after {
|
||||||
|
width: calc(100% + 0px);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
& li.nav-item {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
13
static/template/tmp.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
/* main {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 5em;
|
||||||
|
margin-bottom: 5em;
|
||||||
|
min-height: 85vh;
|
||||||
|
height: 85vh;
|
||||||
|
} */
|
78
templates/about-us/about-us.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='about-us/about-us.css')}}">
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="expanding-panels">
|
||||||
|
<!-- Quiénes Somos -->
|
||||||
|
<div class="panel p1">
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="panel-title">
|
||||||
|
<h3>¿Quiénes Somos?</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-text">
|
||||||
|
<p>Somos Cecilia y Enrique, profesionistas en Capital Humano y Desarrollo Organizacional, con más de 20 años de experiencia impulsando el crecimiento de empresas a través de estrategias de talento 360º.</p>
|
||||||
|
<p>Fundamos {% include 'comps/forma.html' %} para transformar la gestión del talento con soluciones personalizadas, basadas en datos confiables y estrategias innovadoras. A través de alianzas estratégicas y nuestras propias iniciativas, ayudamos a las empresas a tomar decisiones informadas, optimizando su capital humano con herramientas efectivas y de alto impacto.</p>
|
||||||
|
<p>Con {% include 'comps/forma.html' %}, tu equipo no solo crece, sino que evoluciona.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Misión -->
|
||||||
|
<div class="panel p2">
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="panel-title">
|
||||||
|
<h3>Misión</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-text">
|
||||||
|
<p>Impulsar el crecimiento y la competitividad de nuestra clientela a través de soluciones integrales en materia de capital humano. Diseñadas con la más alta calidad, precisión y puntualidad.</p>
|
||||||
|
<p>Destacarnos por brindar asesoría confiable y estratégica, integrando tecnología avanzada y las mejores prácticas del sector para optimizar la gestión del talento y el desarrollo organizacional.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Visión -->
|
||||||
|
<div class="panel p3">
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="panel-title">
|
||||||
|
<h3>Visión</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-text">
|
||||||
|
<p>Ser la consultoría de referencia en recursos humanos, reconocida por nuestra innovación, precisión y compromiso con la excelencia.</p>
|
||||||
|
<p>Nos esforzamos por transformar la manera en que las empresas desarrollan su talento, haciendo que la profesionalización y el crecimiento sean accesibles. A través de soluciones tecnológicas y enfoques estratégicos e innovadores, impulsamos el éxito de nuestros clientes y la evolución del talento humano.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Valores -->
|
||||||
|
<div class="panel p4">
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="panel-title">
|
||||||
|
<h3>Valores</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-text">
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li><b>Excelencia:</b> Nos involucramos con la calidad en cada servicio y producto que entregamos.</li>
|
||||||
|
<li><b>Innovación:</b> Adoptamos tecnología de vanguardia para ofrecer soluciones eficientes y competitivas.</li>
|
||||||
|
<li><b>Responsabilidad:</b> Construimos relaciones de confianza basadas en resultados.</li>
|
||||||
|
<li><b>Precisión:</b> Brindamos diagnósticos, estrategias y resultados con el más alto grado de exactitud.</li>
|
||||||
|
<li><b>Ética:</b> Actuamos con integridad y transparencia en cada interacción con nuestros clientes.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
73
templates/comps/footer.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<style>
|
||||||
|
/* Estilos personalizados para el footer */
|
||||||
|
.footer {
|
||||||
|
background-color: #50164A ; /* Color de fondo oscuro */
|
||||||
|
color: #ffffff; /* Color del texto */
|
||||||
|
padding: 20px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Columna 1: Información de contacto -->
|
||||||
|
<div class="col-md-6 text-center">
|
||||||
|
<h5>Contacto</h5>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<i class="bi bi-telephone-fill"></i> +52 5480-xxxx
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #000000;" width="16" height="16" fill="currentColor" class="bi bi-envelope-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414zM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586zm3.436-.586L16 11.801V4.697z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Columna 2: Enlaces útiles -->
|
||||||
|
<div class="col-md-6 text-center">
|
||||||
|
<h5>Redes Sociales</h5>
|
||||||
|
<div class="r-i-i">
|
||||||
|
<!-- <button onclick="window.location.href='{{ url_for('home') }}'"> </button> -->
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #3b5998;" width="16" height="16" fill="currentColor" class="bi bi-facebook" viewBox="0 0 16 16">
|
||||||
|
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #000000;" width="16" height="16" fill="currentColor" class="bi bi-twitter-x" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #F56040;" width="16" height="16" fill="currentColor" class="bi bi-instagram" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.9 3.9 0 0 0-1.417.923A3.9 3.9 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.9 3.9 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.9 3.9 0 0 0-.923-1.417A3.9 3.9 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599s.453.546.598.92c.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.5 2.5 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.5 2.5 0 0 1-.92-.598 2.5 2.5 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233s.008-2.388.046-3.231c.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92s.546-.453.92-.598c.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92m-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217m0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #FF0000;" width="16" height="16" fill="currentColor" class="bi bi-youtube" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.01 2.01 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.01 2.01 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31 31 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.01 2.01 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A100 100 0 0 1 7.858 2zM6.4 5.209v4.818l4.157-2.408z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-light">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="color: #0e76a8;" width="16" height="16" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
|
||||||
|
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Derechos de autor -->
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12 text-center">
|
||||||
|
<p class="mb-0">© 2025{% include 'comps/forma.html' %}. Todos los derechos reservados.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
1
templates/comps/forma.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<span style="color: #747474;">FORM<span style="color: #47D45A;">H</span><span style="color: #0F9ED5;">ä</span></span>
|
57
templates/comps/navbar.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary navbar-custom">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('home') }}">
|
||||||
|
{% include 'comps/forma.html' %}
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0 effect-3">
|
||||||
|
|
||||||
|
<!-- Inicio -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'home' %}active{% endif %}" aria-current="page" href="{{ url_for('home') }}">Inicio</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nosotros -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'about_us' %}active{% endif %}" href="{{ url_for('about_us') }}">Nosotros</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Soluciones -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'solutions' %}active{% endif %}" href="{{ url_for('solutions') }}">Soluciones</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Metodología -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'methodology' %}active{% endif %}" href="{{ url_for('methodology') }}">Metodología</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Testimoniales -->
|
||||||
|
<!-- <li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">Testimoniales</a>
|
||||||
|
</li> -->
|
||||||
|
|
||||||
|
<!-- Blog -->
|
||||||
|
<!-- <li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">Blog</a>
|
||||||
|
</li> -->
|
||||||
|
|
||||||
|
<!-- Contacto -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'contact' %}active{% endif %}" href="{{url_for('contact')}}">Contacto</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Log In -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == 'login' %}active{% endif %}" href="{{url_for('login')}}">Log In</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
34
templates/comps/navbar_usr.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary navbar-custom">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('home') }}">
|
||||||
|
{% include 'comps/forma.html' %}
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0 effect-3">
|
||||||
|
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-person-circle"></i> Usuario
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
|
||||||
|
<!-- <li><a class="dropdown-item" href="#">Another action</a></li> -->
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<!-- <li><a class="dropdown-item" href="#">Something else here</a></li> -->
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="{{ url_for('logout') }}">
|
||||||
|
<i class="bi bi-door-open-fill"></i> Logout
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Asegúrate de tener esto al final del body -->
|
||||||
|
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> -->
|
172
templates/contact/contact.html
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
{% block css %}
|
||||||
|
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
font-family: 'Segoe UI', Roboto, sans-serif;
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
flex: 0 0 180px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4299e1;
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
margin: 1.5rem auto 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #4299e1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<div class="form-header">
|
||||||
|
<h2>Contáctanos</h2>
|
||||||
|
<p>Déjanos tus datos y nos pondremos en contacto contigo</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mensajes Flash -->
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('contact') }}">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
|
<!-- {# Nombre #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.nombre.label(class_="form-label") }}
|
||||||
|
{{ form.nombre(placeholder="Tu nombre", class_="form-control", maxlength="30") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Apellido #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.apellido.label(class_="form-label") }}
|
||||||
|
{{ form.apellido(placeholder="Tu apellido", class_="form-control", maxlength="50") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Email #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.email.label(class_="form-label") }}
|
||||||
|
{{ form.email(placeholder="ejemplo@email.com", class_="form-control", maxlength="35") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Estado #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.estado.label(class_="form-label") }}
|
||||||
|
{{ form.estado(class_="form-select", maxlength="35") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Número telefónico #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.num_tel.label(class_="form-label") }}
|
||||||
|
{{ form.num_tel(placeholder="+52 55 1234 5678", class_="form-control", maxlength="16") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Tamaño de la compañia #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.size_co.label(class_="form-label") }}
|
||||||
|
{{ form.size_co(class_="form-select", maxlength="40") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# rol del contacto dentro de la empresa #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.rol_contacto.label(class_="form-label") }}
|
||||||
|
{{ form.rol_contacto(placeholder="Tu puesto o rol", class_="form-select", maxlength="50") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- {# Sector de la empresa #} -->
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.industry_type.label(class_="form-label") }}
|
||||||
|
{{ form.industry_type(placeholder="Sector de tu empresa", class_="form-select", maxlength="40") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
{{ form.tipo_req.label(class_="form-label") }}
|
||||||
|
{{ form.tipo_req(placeholder="Describe cómo podemos ayudarte...", class_="form-control", maxlength="250") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" >
|
||||||
|
Enviar Mensaje
|
||||||
|
<i class="bi bi-send-check-fill"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock body %}
|
74
templates/home/home.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='home/home.css')}}">
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="r-i">
|
||||||
|
<div class="r-i-ii">
|
||||||
|
<spam>
|
||||||
|
Potenciamos el talento con soluciones personalizadas que alinean personas, información y propósito. Diseñamos
|
||||||
|
estrategias basadas en información, ordenada, confiable, disponible y segura, dando FORMHä a tus decisiones.
|
||||||
|
</spam>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="r-ii">
|
||||||
|
<!-- Tarjeta 1 -->
|
||||||
|
<div class="card-flyer">
|
||||||
|
<div class="image-box">
|
||||||
|
<img src="{{url_for('static', filename='img/home/software.avif')}}" alt="Software">
|
||||||
|
</div>
|
||||||
|
<div class="text-container">
|
||||||
|
<h6>
|
||||||
|
Actualización del proceso de reclutamiento y selección.
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
Actualmente desde la AI hasta las técnicas de entrevista, han abierto un nuevo horizonte en el marco del
|
||||||
|
reclutamiento y selección de personal…
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 2 -->
|
||||||
|
<div class="card-flyer">
|
||||||
|
<div class="image-box">
|
||||||
|
<img src="{{url_for('static', filename='img/home/work.avif')}}" alt="Productividad">
|
||||||
|
</div>
|
||||||
|
<div class="text-container">
|
||||||
|
<h6>
|
||||||
|
Beneficios e impacto en la productividad.
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
En la medida que adoptemos la NOM 035, factores de riesgos psicosocial en el trabajo, identificación y
|
||||||
|
prevención; como una guía para optimizar el clima y…
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 3 -->
|
||||||
|
<div class="card-flyer">
|
||||||
|
<div class="image-box">
|
||||||
|
<img src="{{url_for('static', filename='img/home/chair.avif')}}" alt="Ley Silla">
|
||||||
|
</div>
|
||||||
|
<div class="text-container">
|
||||||
|
<h6>
|
||||||
|
Alcances y objetivos de la Ley Silla.
|
||||||
|
</h6>
|
||||||
|
<p>
|
||||||
|
El pasado 19 de diciembre de 2024, se publico en el Diario Oficial de la Federación el decreto por el cual
|
||||||
|
se reforman y adicionan diversas…
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
203
templates/login/login.html
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #4361ee;
|
||||||
|
--primary-dark: #3a56d4;
|
||||||
|
--text-color: #2b2d42;
|
||||||
|
--light-gray: #f8f9fa;
|
||||||
|
--white: #ffffff;
|
||||||
|
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subtitle {
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 12px 12px 40px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-footer {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- En tu template (frontend) -->
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('login') }}" class="login-form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
|
<h2 class="form-title">Bienvenido de vuelta</h2>
|
||||||
|
<p class="form-subtitle">Ingresa tus credenciales para continuar</p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.email.label(class="form-label") }}
|
||||||
|
<div class="input-container">
|
||||||
|
<svg class="input-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||||
|
<polyline points="22,6 12,13 2,6"></polyline>
|
||||||
|
</svg>
|
||||||
|
{{ form.email(class="form-input", placeholder="ejemplo@correo.com", type="email") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.password.label(class="form-label") }}
|
||||||
|
<div class="input-container">
|
||||||
|
<svg class="input-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||||
|
</svg>
|
||||||
|
{{ form.password(class="form-input", placeholder="••••••••") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="submit-btn">
|
||||||
|
Iniciar Sesión
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="btn-icon">
|
||||||
|
<path d="M5 12h14M12 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="form-footer">
|
||||||
|
<a href="{{ url_for('recover_pswd') }}" class="forgot-password">¿Olvidaste tu contraseña?</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock js %}
|
62
templates/login/recover_pswd.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- En tu template (frontend) -->
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }}">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('recover_pswd') }}" class="w-100 mx-auto p-4 border rounded shadow" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form.email.label(class="form-label fw-bold") }}
|
||||||
|
{{ form.email(
|
||||||
|
class="form-control" + (" is-invalid" if form.email.errors else ""),
|
||||||
|
**{"aria-describedby": "emailHelp"}
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{% if form.email.errors %}
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{% for error in form.email.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="emailHelp" class="form-text">{{ form.email.description }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
|
||||||
|
<i class="bi bi-envelope-fill me-2"></i> Recuperar contraseña
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<a href="{{ url_for('login') }}" class="text-decoration-none">
|
||||||
|
<i class="bi bi-arrow-left me-1"></i> Regresar al Log In
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
{% endblock js %}
|
165
templates/methodology/methodology.html
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='methodology/methodology.css')}}">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.methodology-section {
|
||||||
|
padding: 5rem 0;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title h2 {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title h2:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 50px;
|
||||||
|
height: 3px;
|
||||||
|
background: #4f46e5;
|
||||||
|
margin: 10px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title .lead {
|
||||||
|
color: #4a5568;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.methodology-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
height: 100%;
|
||||||
|
border-left: 4px solid #4f46e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.methodology-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #4f46e5;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
color: #4a5568;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-card {
|
||||||
|
border-left-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-card .card-icon {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<section class="methodology-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-title">
|
||||||
|
<h2>METODOLOGÍA</h2>
|
||||||
|
<p class="lead">Evaluamos cada solución para asegurar que generamos valor en tiempo, costos, calidad y/o productividad</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Tarjeta 1 -->
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="methodology-card">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi bi-bar-chart-line"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title">ANÁLISIS DE DATOS</h3>
|
||||||
|
<p class="card-text">Identificamos información clave que nos permita comprender las necesidades, metas y retos de tu empresa. Así como datos estratégicos.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 2 -->
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="methodology-card highlight-card">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi bi-sliders"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title">PERSONALIZACIÓN</h3>
|
||||||
|
<p class="card-text">Diseñamos e implementamos estrategias personalizadas para potenciar el valor de tu empresa, combinando metodologías especializadas y tecnología.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 3 -->
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="methodology-card">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi bi-people"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title">EXPERIENCIA DE SERVICIO</h3>
|
||||||
|
<p class="card-text">Aseguraremos que cada solución, proyecto o implementación cuente con una asesoría cercana, con una experiencia de servicio ágil y personalizada tanto para nuestros clientes como usuarios.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 4 -->
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="methodology-card">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi bi-graph-up-arrow"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title">IMPACTO</h3>
|
||||||
|
<p class="card-text">Mostramos resultados tangibles de nuestra intervención con métricas claras y objetivos alcanzables.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta 5 -->
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="methodology-card">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi bi-arrow-repeat"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title">SOSTENIBILIDAD DEL CAMBIO</h3>
|
||||||
|
<p class="card-text">Damos seguimiento a la implementación, reforzando capacidades internas y midiendo avances. Buscamos que cada intervención deje capacidades instaladas y valor.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
210
templates/solutions/solutions.html
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<!-- {# modal bootstrap #} -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='solutions/solutions.css')}}">
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="parent">
|
||||||
|
<div class="i-i" data-toggle="modal" data-target="#forma-reclutamiento"></div>
|
||||||
|
<div class="i-ii" data-toggle="modal" data-target="#forma-liderazgo"></div>
|
||||||
|
<div class="i-iii" data-toggle="modal" data-target="#forma-capacitacion"></div>
|
||||||
|
<div class="ii-i" data-toggle="modal" data-target="#forma-cambio"></div>
|
||||||
|
<div class="ii-ii" data-toggle="modal" data-target="#forma-objetivos"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- da forma reclutamiento -->
|
||||||
|
<div class="modal fade" id="forma-reclutamiento" tabindex="-1" role="dialog" aria-labelledby="forma-reclutamientoTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLongTitle">Impulsa tu equipo con un reclutamiento más preciso, ágil y estratégico.</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
En {% include 'comps/forma.html' %}, combinamos tecnología y metodologías avanzadas para atraer, identificar y seleccionar talento de alto
|
||||||
|
potencial.
|
||||||
|
<ul>
|
||||||
|
<li><b>Plataformas digitales e inteligencia artificial:</b> Utilizamos herramientas especializadas, para optimizar
|
||||||
|
la búsqueda y selección de candidatos.</li>
|
||||||
|
<li><b>Entrevistas estructuradas y por competencias:</b> Aplicamos diversas técnicas, como entrevistas iniciales,
|
||||||
|
por competencias, modelo SMART y entrevistas profundas para evaluar habilidades, experiencia y valores.</li>
|
||||||
|
<li><b>Evaluaciones psicométricas:</b> Medimos aptitudes cognitivas, rasgos de personalidad y estilos de liderazgo
|
||||||
|
para garantizar el ajuste ideal al puesto.</li>
|
||||||
|
<li><b>Assessment Center:</b> Implementamos dinámicas grupales, simulaciones y ejercicios de toma de decisiones
|
||||||
|
para
|
||||||
|
evaluar el desempeño real en situaciones laborales.</li>
|
||||||
|
<li><b>Estudios socioeconómicos digitales:</b> Realizamos verificaciones detalladas mediante plataformas digitales
|
||||||
|
para confirmar antecedentes laborales, referencias y nivel socioeconómico, asegurando procesos confiables y
|
||||||
|
ágiles.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- da forma a tu liderazgo-->
|
||||||
|
<div class="modal fade" id="forma-liderazgo" tabindex="-1" role="dialog" aria-labelledby="forma-liderazgoTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLongTitle">Da {% include 'comps/forma.html' %} a tu liderazgo.</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Prepara a tus líderes para afrontar los desafíos del entorno empresarial actual con estrategias que potencian su
|
||||||
|
crecimiento y efectividad. En {% include 'comps/forma.html' %}, diseñamos soluciones para identificar, desarrollar y fortalecer el liderazgo
|
||||||
|
en todos los niveles de la organización. <br>
|
||||||
|
Nos especializamos en:
|
||||||
|
<ul>
|
||||||
|
<li>Mentorías de talento femenino.</li>
|
||||||
|
<li>Assessment.</li>
|
||||||
|
<li>Planes de desarrollo de talento.</li>
|
||||||
|
<li>Nine Box.</li>
|
||||||
|
<li>Programas de liderazgo especializado.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- da forma a tu capacitación-->
|
||||||
|
<div class="modal fade" id="forma-capacitacion" tabindex="-1" role="dialog" aria-labelledby="forma-capacitacionTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLongTitle">Da {% include 'comps/forma.html' %} a tu capacitación. </h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Impulsamos el desarrollo de habilidades como clave del crecimiento empresarial, promoviendo una cultura de
|
||||||
|
aprendizaje, en donde cada colaborador asume un rol activo en su evolución profesional, cómo lo hacemos:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Análisis de necesidades: Identificamos los retos específicos de tu equipo para diseñar programas a la medida.
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Contenidos teórico-prácticos:</b> Estructuramos materiales que combinan teoría con aplicación real.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Metodologías innovadoras:</b> Aplicamos enfoques de vanguardia para maximizar el aprendizaje, tales como;
|
||||||
|
Microlearning, Gamificación, E-learning y Blended Learning
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
Nuestro Top10 de formación:
|
||||||
|
<ul>
|
||||||
|
<li>Liderazgo y gestión de equipos.</li>
|
||||||
|
<li>Desarrollo de up, soft y hard skills.</li>
|
||||||
|
<li>Gestión del cambio.</li>
|
||||||
|
<li>Transformación digital y competencias tecnológicas.</li>
|
||||||
|
<li>Metodologías ágiles y gestión de proyectos.</li>
|
||||||
|
<li>Servicio y experiencia del cliente.</li>
|
||||||
|
<li>Diversidad, equidad e inclusión.</li>
|
||||||
|
<li>Accesibilidad.</li>
|
||||||
|
<li>Seguridad y bienestar laboral.</li>
|
||||||
|
<li>Temas específicos y especializados en Capital Humano.</li>
|
||||||
|
</ul>
|
||||||
|
Ya sea en capacitaciones, escuelas o universidades, contamos con experiencia en diversas estructuras
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- da forma al cambio-->
|
||||||
|
<div class="modal fade" id="forma-cambio" tabindex="-1" role="dialog" aria-labelledby="forma-cambioTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLongTitle">Simplifica, optimiza y fortalece tu capital humano con asesoría especializada.</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Facilitamos en las organizaciones la optimización y simplificación de sus procesos mediante un enfoque
|
||||||
|
estratégico, metodologías ágiles y soluciones tecnológicas. Nuestras soluciones incluyen:
|
||||||
|
<ul>
|
||||||
|
<li>Uso estratégico de tecnología en los procesos</li>
|
||||||
|
<li>Estudios y estrategias para optimización de la eficiencia operativa</li>
|
||||||
|
<li>Desarrollo e implementación de normas, políticas y procedimientos</li>
|
||||||
|
<li>Desarrollo y optimización de áreas de Capital Humano</li>
|
||||||
|
<li>Diseño de estructuras organizacionales. Trazabilidad de roles, responsabilidades y procesos clave.</li>
|
||||||
|
<li>Asesoría laboral y legal</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- da forma a tus objetivos-->
|
||||||
|
<div class="modal fade" id="forma-objetivos" tabindex="-1" role="dialog" aria-labelledby="forma-objetivosTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLongTitle">Convierte datos en decisiones estratégicas para impulsar el crecimiento de tu empresa</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Transformamos la información en estrategias accionables. Nuestras soluciones se basan en la recolección y análisis de datos, utilizando herramientas digitales y metodologías estadísticas para generar diagnósticos precisos y planes de acción estratégicos.
|
||||||
|
<ul>
|
||||||
|
<li>Detección de Necesidades de Capacitación.</li>
|
||||||
|
<li>Evaluación de engagement, clima y cultura.</li>
|
||||||
|
<li>Evaluación de Desempeño</li>
|
||||||
|
<li>Integración con la NOM 035-STPS-2018.</li>
|
||||||
|
<li>Intervenciones grupales e individuales de Desarrollo Organizacional (DO)</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<!-- 2. Al final del BODY (en este orden) -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||||
|
<!-- <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js"></script>
|
||||||
|
{% endblock js %}
|
64
templates/template.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title>
|
||||||
|
{% block title %}{{ title | default('FORMa') }}{% endblock %}
|
||||||
|
</title>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- {# i bootstrap #} -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<!-- {# icons #}} -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
<!-- {# f bootstrap #} -->
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- {# i navbar css #} -->
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='template/tmp.css')}}">
|
||||||
|
<!-- {# navbar #} -->
|
||||||
|
<link rel="stylesheet" href="{{url_for('static', filename='template/navbar.css')}}">
|
||||||
|
<!-- {# f navbar css #} -->
|
||||||
|
|
||||||
|
<!-- {# i notify css #} -->
|
||||||
|
<!-- CSS -->
|
||||||
|
<!-- {# doc: https://github.com/simple-notify/simple-notify?tab=readme-ov-file #} -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-notify/dist/simple-notify.css" />
|
||||||
|
<!-- {# f notify css #} -->
|
||||||
|
<!-- {# i notify js #} -->
|
||||||
|
<!-- JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/simple-notify/dist/simple-notify.min.js"></script>
|
||||||
|
<!-- {# f notify js #} -->
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<!-- <body> -->
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
<!-- <main> -->
|
||||||
|
<main>
|
||||||
|
{% block body %}
|
||||||
|
{% endblock body %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% include 'comps/footer.html' %}
|
||||||
|
<!-- {# i bootstrap js #} -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||||
|
<!-- {# f bootstrap js #} -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{% endblock js %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
27
templates/usr_home/usr_home.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'template.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
|
||||||
|
{% endblock css %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include 'comps/navbar_usr.html' %}
|
||||||
|
{% endblock navbar %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<!-- {% if current_user %}
|
||||||
|
<h2>Bienvenido, {{ current_user }}</h2>
|
||||||
|
{% endif %} -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock js %}
|