Initial commit

This commit is contained in:
2025-11-09 17:52:30 +01:00
parent 22b558ed78
commit a2e823c4c3
21 changed files with 5458 additions and 328 deletions

10
frontend/src/App.jsx Normal file
View File

@@ -0,0 +1,10 @@
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
const App = () => {
return (
<></>
)
}
export default App

131
frontend/src/css/index.css Normal file
View File

@@ -0,0 +1,131 @@
/* ================================
FUENTES PERSONALIZADAS
================================== */
@font-face {
font-family: "Open Sans";
src: url('/fonts/OpenSans.ttf');
}
@font-face {
font-family: "Product Sans";
src: url('/fonts/ProductSansRegular.ttf');
}
@font-face {
font-family: "Product Sans Italic";
src: url('/fonts/ProductSansItalic.ttf');
}
@font-face {
font-family: "Product Sans Italic Bold";
src: url('/fonts/ProductSansBoldItalic.ttf');
}
@font-face {
font-family: "Product Sans Bold";
src: url('/fonts/ProductSansBold.ttf');
}
/* ================================
PALETA DE COLORES
================================== */
:root {
/* Colores base */
--bondi-blue-light: #5bc0da;
--bondi-blue: #359ebc;
--bondi-blue-dark: #0c7fa3;
--bondi-blue-darker: #09698a;
--gun-powder-lighter: #686983;
--gun-powder-light: #4e5067;
--gun-powder: #3d3c52;
--gun-powder-dark: #303145;
/* 🎨 Fondos */
--background-main: var(--gun-powder-dark);
--background-alt: var(--gun-powder);
--background-card: var(--gun-powder-light);
--background-hover: var(--bondi-blue-dark);
--background-accent: var(--bondi-blue);
/* ✍️ Texto */
--text-main: #f0f4f8;
--text-muted: #c2c6d1;
--text-inverted: #0d1117;
--text-accent: var(--bondi-blue-light);
--text-link: var(--bondi-blue);
/* 🪞 Bordes */
--border-color: #2c2d3e;
--border-hover: var(--bondi-blue-dark);
--border-accent: var(--bondi-blue);
/* 💡 Sombras */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
--shadow-md: 0 2px 6px rgba(0, 0, 0, 0.35);
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.45);
--shadow-accent: 0 0 12px rgba(53, 158, 188, 0.4);
/* 🔘 Botones */
--button-bg: var(--bondi-blue);
--button-bg-hover: var(--bondi-blue-dark);
--button-text: #ffffff;
--button-disabled: #5f7080;
/* 🧭 Inputs */
--input-bg: var(--gun-powder);
--input-border: var(--gun-powder-lighter);
--input-border-focus: var(--bondi-blue);
--input-text: var(--text-main);
--input-placeholder: #a0a4b2;
/* 🚨 Estados */
--success: #4cc9b0;
--warning: #f3c969;
--error: #e85d75;
--info: var(--bondi-blue-light);
}
/* ================================
ESTILOS BASE / RESET SUAVE
================================== */
html {
font-family: "Open Sans", sans-serif;
color: var(--text-main);
background-color: var(--background-main);
}
body {
font-family: "Open Sans", sans-serif;
color: var(--text-main);
background-color: transparent !important;
}
main {
color: var(--text-main);
background-color: var(--background-main);
}
/* Tipografía global */
div,
label,
input,
p,
span,
a,
button {
font-family: "Open Sans", sans-serif;
color: var(--text-main);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Product Sans", sans-serif;
color: var(--text-main);
}

View File

@@ -0,0 +1,130 @@
import { useState, useEffect, useCallback, useRef } from "react";
import axios from "axios";
export const useData = (config) => {
const [data, setData] = useState(null);
const [dataLoading, setLoading] = useState(true);
const [dataError, setError] = useState(null);
const configRef = useRef();
useEffect(() => {
if (config?.baseUrl) {
configRef.current = config;
}
}, [config]);
const getAuthHeaders = () => ({
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.getItem("token")}`,
});
const fetchData = useCallback(async () => {
const current = configRef.current;
if (!current?.baseUrl) return;
setLoading(true);
setError(null);
try {
const response = await axios.get(current.baseUrl, {
headers: getAuthHeaders(),
params: current.params,
});
setData(response.data.data);
} catch (err) {
setError(err.response?.data?.message || err.message);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (config?.baseUrl) {
fetchData();
}
}, [config, fetchData]);
const getData = async (url, params = {}) => {
try {
const response = await axios.get(url, {
headers: getAuthHeaders(),
params,
});
return { data: response.data.data, error: null };
} catch (err) {
return {
data: null,
error: err.response?.data?.message || err.message,
};
}
};
const postData = async (endpoint, payload) => {
const headers = {
Authorization: `Bearer ${localStorage.getItem("token")}`,
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
};
const response = await axios.post(endpoint, payload, { headers });
await fetchData();
return response.data.data;
};
const postDataValidated = async (endpoint, payload) => {
try {
const headers = {
Authorization: `Bearer ${localStorage.getItem("token")}`,
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
};
const response = await axios.post(endpoint, payload, { headers });
return { data: response.data.data, errors: null };
} catch (err) {
const raw = err.response?.data?.message;
let parsed = {};
try {
parsed = JSON.parse(raw);
} catch {
return { data: null, errors: { general: raw || err.message } };
}
return { data: null, errors: parsed };
}
};
const putData = async (endpoint, payload) => {
const response = await axios.put(endpoint, payload, {
headers: getAuthHeaders(),
});
await fetchData();
return response.data.data;
};
const deleteData = async (endpoint) => {
const response = await axios.delete(endpoint, {
headers: getAuthHeaders(),
});
await fetchData();
return response.data.data;
};
const deleteDataWithBody = async (endpoint, payload) => {
const response = await axios.delete(endpoint, {
headers: getAuthHeaders(),
data: payload,
});
await fetchData();
return response.data.data;
};
return {
data,
dataLoading,
dataError,
getData,
postData,
postDataValidated,
putData,
deleteData,
deleteDataWithBody,
};
};

13
frontend/src/main.jsx Normal file
View File

@@ -0,0 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import '@/css/index.css'
import App from '@/App.jsx'
import { BrowserRouter } from 'react-router-dom'
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
)