This commit is contained in:
David Arranz 2025-06-11 15:13:21 +02:00
parent 0fdf18baf3
commit 484f0119a7
31 changed files with 165 additions and 107 deletions

12
.vscode/launch.json vendored
View File

@ -1,12 +1,20 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "WEB: Run in Development Mode",
"type": "node",
"request": "launch",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "dev", "--filter", "web"],
"console": "integratedTerminal"
},
{ {
"name": "SERVER: Run in Development Mode", "name": "SERVER: Run in Development Mode",
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"runtimeExecutable": "npm", "runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "dev"], "runtimeArgs": ["run", "dev", "--filter", "server"],
"console": "integratedTerminal" "console": "integratedTerminal"
}, },
{ {

View File

@ -42,6 +42,7 @@
"dependencies": { "dependencies": {
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@erp/auth": "workspace:*", "@erp/auth": "workspace:*",
"@erp/invoices": "workspace:*",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"cls-rtracer": "^2.6.3", "cls-rtracer": "^2.6.3",
"cors": "^2.8.5", "cors": "^2.8.5",

View File

@ -5,9 +5,12 @@ import os from "node:os";
import { createApp } from "./app"; import { createApp } from "./app";
import { ENV } from "./config"; import { ENV } from "./config";
import { tryConnectToDatabase } from "./config/database"; import { tryConnectToDatabase } from "./config/database";
import { listRoutes } from "./lib";
import { initModules } from "./lib/modules"; import { initModules } from "./lib/modules";
import { registerModules } from "./register-modules"; import { registerModules } from "./register-modules";
const API_BASE_PATH = "/api/v1";
// Guardamos información del estado del servidor // Guardamos información del estado del servidor
export const currentState = { export const currentState = {
launchedAt: DateTime.now(), launchedAt: DateTime.now(),
@ -129,7 +132,10 @@ process.on("uncaughtException", (error: Error) => {
// initStructure(sequelizeConn.connection); // initStructure(sequelizeConn.connection);
// insertUsers(); // insertUsers();
await initModules({ app, database, baseRoutePath: "/api/v1", logger }); await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger });
logger.info("holaaaaaaaaaaaaaaaaa");
console.log(listRoutes(app._router, API_BASE_PATH));
server.listen(currentState.port, () => { server.listen(currentState.port, () => {
server.emit("listening"); server.emit("listening");

View File

@ -1,2 +1,3 @@
export * from "./list-routes";
export * from "./logger"; export * from "./logger";
export * from "./modules"; export * from "./modules";

View File

@ -0,0 +1,30 @@
// Función para listar rutas
export function listRoutes(appOrRouter, basePath = "") {
const routes = [];
appOrRouter.stack.forEach((middleware) => {
if (middleware.route) {
// Es una ruta directa
const methods = Object.keys(middleware.route.methods).map((m) => m.toUpperCase());
routes.push({
path: basePath + middleware.route.path,
methods,
});
} else if (middleware.name === "router" && middleware.handle.stack) {
// Es un router anidado
const newBasePath =
basePath +
(middleware.regexp?.source !== "^\\/?$"
? middleware.regexp
?.toString()
.replace(/^\/\^\\/, "")
.replace(/\\\/\?\(\?=\\\/\|\$\)\/i$/, "")
.replace(/\\\//g, "/")
: "");
const childRoutes = listRoutes(middleware.handle, basePath + (middleware?.path || ""));
routes.push(...childRoutes);
}
});
return routes;
}

View File

@ -7,10 +7,10 @@ const registeredModules: Map<string, IModuleServer> = new Map();
const initializedModules = new Set<string>(); const initializedModules = new Set<string>();
export function registerModule(pkg: IModuleServer) { export function registerModule(pkg: IModuleServer) {
if (registeredModules.has(pkg.metadata.name)) { if (registeredModules.has(pkg.name)) {
throw new Error(`❌ Paquete "${pkg.metadata.name}" ya registrado.`); throw new Error(`❌ Paquete "${pkg.name}" ya registrado.`);
} }
registeredModules.set(pkg.metadata.name, pkg); registeredModules.set(pkg.name, pkg);
} }
export async function initModules(params: ModuleParams) { export async function initModules(params: ModuleParams) {
@ -27,7 +27,7 @@ const loadModule = (name: string, params: ModuleParams) => {
if (!pkg) throw new Error(`❌ Paquete "${name}" no encontrado.`); if (!pkg) throw new Error(`❌ Paquete "${name}" no encontrado.`);
// Resolver dependencias primero // Resolver dependencias primero
const deps = pkg.metadata.dependencies || []; const deps = pkg.dependencies || [];
deps.forEach((dep) => loadModule(dep, params)); deps.forEach((dep) => loadModule(dep, params));
// Inicializar el module // Inicializar el module
@ -44,7 +44,7 @@ const loadModule = (name: string, params: ModuleParams) => {
if (pkgApi?.services) { if (pkgApi?.services) {
const services = pkgApi.services; const services = pkgApi.services;
if (services && typeof services === "object") { if (services && typeof services === "object") {
registerService(pkg.metadata.name, services); registerService(pkg.name, services);
} }
} }

View File

@ -1,8 +1,8 @@
import { authAPIModule } from "@erp/auth/api"; import { authAPIModule } from "@erp/auth/api";
import { invoicesAPIModule } from "@erp/invoices/api";
import { registerModule } from "./lib"; import { registerModule } from "./lib";
//import { invoicesAPIModule } from "@erp/invoices/api";
export const registerModules = () => { export const registerModules = () => {
registerModule(authAPIModule); registerModule(authAPIModule);
//registerModule(invoicesAPIModule); registerModule(invoicesAPIModule);
}; };

View File

@ -1,5 +1,5 @@
import { AuthModuleManifiest } from "@erp/auth/client"; import { AuthModuleManifiest } from "@erp/auth/client";
import { IModuleClient } from "@erp/core/client"; import { IModuleClient } from "@erp/core/client";
//import InvoicesModule from "@erp/invoices/client"; import { InvoicesModuleManifiest } from "@erp/invoices/client";
export const modules: IModuleClient[] = [AuthModuleManifiest]; export const modules: IModuleClient[] = [AuthModuleManifiest, InvoicesModuleManifiest];

View File

@ -32,6 +32,8 @@ export const AppRoutes = (): JSX.Element => {
const grouped = groupModulesByLayout(modules); const grouped = groupModulesByLayout(modules);
console.log(grouped);
return ( return (
<Router> <Router>
<ScrollToTop /> <ScrollToTop />

View File

@ -1,11 +1,9 @@
import { IModuleServer, ModuleParams } from "@erp/core"; import { IModuleServer, ModuleParams } from "@erp/core/api";
export const authAPIModule: IModuleServer = { export const authAPIModule: IModuleServer = {
metadata: { name: "auth",
name: "auth", version: "1.0.0",
version: "1.0.0", dependencies: [],
dependencies: [],
},
init(params: ModuleParams) { init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts"); // const contacts = getService<ContactsService>("contacts");
const { logger } = params; const { logger } = params;

View File

@ -1,12 +1,12 @@
import { PropsWithChildren, createContext, useEffect, useState } from "react"; import { PropsWithChildren, createContext, useEffect, useState } from "react";
import { IAuthService } from "../services"; import { IAuthService } from "../services";
export interface AuthContextType { export type AuthContextType = {
token: string | null; token: string | null;
isAuthenticated: boolean; isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>; login: (email: string, password: string) => Promise<void>;
logout: () => void; logout: () => void;
} };
export interface AuthContextParams { export interface AuthContextParams {
authService: IAuthService; authService: IAuthService;
@ -20,7 +20,10 @@ export const AuthContext = createContext<AuthContextType | undefined>(undefined)
/** /**
* Proveedor de autenticación para toda la app. * Proveedor de autenticación para toda la app.
*/ */
export const AuthProvider = ({ params, children }: PropsWithChildren<{ params: any }>) => { export const AuthProvider = ({
params,
children,
}: PropsWithChildren<{ params: AuthContextParams }>) => {
const { getAccessToken, setAccessToken, clearAccessToken, authService } = params; const { getAccessToken, setAccessToken, clearAccessToken, authService } = params;
const [token, setToken] = useState<string | null>(getAccessToken()); const [token, setToken] = useState<string | null>(getAccessToken());
@ -30,9 +33,9 @@ export const AuthProvider = ({ params, children }: PropsWithChildren<{ params: a
}, []); }, []);
const login = async (email: string, password: string) => { const login = async (email: string, password: string) => {
const { access_token } = await authService.login({ email, password }); const { token } = await authService.login({ email, password });
setAccessToken(access_token); setAccessToken(token);
setToken(access_token); setToken(token);
}; };
const logout = () => { const logout = () => {

View File

@ -7,7 +7,7 @@ import { AuthContext, AuthContextType } from "../context";
export const useAuth = (): AuthContextType => { export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (!context) { if (!context) {
throw new Error("useAuth debe usarse dentro de <AuthProvider>"); throw new Error("useAuth must be used within a AuthProvider");
} }
return context; return context;
}; };

View File

@ -1,4 +1,4 @@
import { ITransactionManager } from "@erp/core"; import { ITransactionManager } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";

View File

@ -1,12 +1,11 @@
import { IModuleServer, ModuleParams } from "@erp/core"; import { IModuleServer, ModuleParams } from "@erp/core/api";
import { invoicesRouter, models } from "./infrastructure"; import { invoicesRouter, models } from "./infrastructure";
export const invoicesAPIModule: IModuleServer = { export const invoicesAPIModule: IModuleServer = {
metadata: { name: "invoices",
name: "invoices", version: "1.0.0",
version: "1.0.0", dependencies: [],
dependencies: [],
},
init(params: ModuleParams) { init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts"); // const contacts = getService<ContactsService>("contacts");
const { logger } = params; const { logger } = params;

View File

@ -1,4 +1,4 @@
import { ModuleParams } from "@erp/core"; import { ModuleParams } from "@erp/core/api";
import { Application, NextFunction, Request, Response, Router } from "express"; import { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { buildListInvoicesController } from "../../presentation"; import { buildListInvoicesController } from "../../presentation";
@ -21,6 +21,8 @@ export const invoicesRouter = (params: ModuleParams) => {
} }
); );
app.use(`${baseRoutePath}/invoices`, routes);
/*routes.get( /*routes.get(
"/:invoiceId", "/:invoiceId",
//checkTabContext, //checkTabContext,
@ -59,6 +61,4 @@ export const invoicesRouter = (params: ModuleParams) => {
buildDeleteInvoiceController().execute(req, res, next); buildDeleteInvoiceController().execute(req, res, next);
} }
);*/ );*/
app.use(`${baseRoutePath}/invoices`, routes);
}; };

View File

@ -1,4 +1,4 @@
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core"; import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { InferCreationAttributes } from "sequelize"; import { InferCreationAttributes } from "sequelize";

View File

@ -1,4 +1,4 @@
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core"; import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
import { UniqueID, UtcDate } from "@repo/rdx-ddd"; import { UniqueID, UtcDate } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../../domain"; import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../../domain";

View File

@ -1,4 +1,4 @@
import { SequelizeRepository } from "@erp/core"; import { SequelizeRepository } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";

View File

@ -1,4 +1,4 @@
import { ExpressController } from "@erp/core"; import { ExpressController } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { GetInvoiceUseCase } from "../../application"; import { GetInvoiceUseCase } from "../../application";
import { IGetInvoicePresenter } from "./presenter"; import { IGetInvoicePresenter } from "./presenter";

View File

@ -1,4 +1,4 @@
import { SequelizeTransactionManager } from "@erp/core"; import { SequelizeTransactionManager } from "@erp/core/api";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { InvoiceService } from "../../domain"; import { InvoiceService } from "../../domain";
import { InvoiceRepository, invoiceMapper } from "../../infrastructure"; import { InvoiceRepository, invoiceMapper } from "../../infrastructure";

View File

@ -1,4 +1,4 @@
import { SequelizeTransactionManager } from "@erp/core"; import { SequelizeTransactionManager } from "@erp/core/api";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { ListInvoicesUseCase } from "../../application"; import { ListInvoicesUseCase } from "../../application";
import { InvoiceService } from "../../domain"; import { InvoiceService } from "../../domain";

View File

@ -1,4 +1,4 @@
import { ExpressController } from "@erp/core"; import { ExpressController } from "@erp/core/api";
import { ListInvoicesUseCase } from "../../application"; import { ListInvoicesUseCase } from "../../application";
import { IListInvoicesPresenter } from "./presenter"; import { IListInvoicesPresenter } from "./presenter";

View File

@ -1,4 +1,4 @@
import { IListResponseDTO } from "@erp/core"; import { IListResponseDTO } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { Collection } from "@repo/rdx-utils"; import { Collection } from "@repo/rdx-utils";
import { IListInvoicesResponseDTO } from "../../../../common/dto"; import { IListInvoicesResponseDTO } from "../../../../common/dto";

View File

@ -1,10 +1,10 @@
import { Outlet } from "react-router-dom"; import { PropsWithChildren } from "react";
import { InvoicesProvider } from "../hooks";
export const InvoicesLayout = () => { export const InvoicesLayout = ({ children }: PropsWithChildren) => {
return ( return (
<InvoicesProvider> <>
<Outlet /> <h1>hola</h1>
</InvoicesProvider> {children}
</>
); );
}; };

View File

@ -0,0 +1 @@
export * from "./invoices-context";

View File

@ -0,0 +1,21 @@
import { usePagination } from "@erp/core/client";
import { PropsWithChildren, createContext } from "react";
export type InvoicesContextType = {};
export const InvoicesContext = createContext<InvoicesContextType | null>(null);
export const InvoicesProvider = ({ children }: PropsWithChildren) => {
const [pagination, setPagination] = usePagination();
return (
<InvoicesContext.Provider
value={{
pagination,
setPagination,
}}
>
{children}
</InvoicesContext.Provider>
);
};

View File

@ -1,27 +1,11 @@
import { usePagination } from "@erp/core/hooks"; import { useContext } from "react";
import { PropsWithChildren, createContext, useContext } from "react"; import { InvoicesContext, InvoicesContextType } from "../context";
export type IInvoicesContextState = {}; export const useInvoices = (): InvoicesContextType => {
export const InvoicesContext = createContext<IInvoicesContextState | null>(null);
export const InvoicesProvider = ({ children }: PropsWithChildren) => {
const [pagination, setPagination] = usePagination();
return (
<InvoicesContext.Provider
value={{
pagination,
setPagination,
}}
>
{children}
</InvoicesContext.Provider>
);
};
export const useInvoicesContext = () => {
const context = useContext(InvoicesContext); const context = useContext(InvoicesContext);
if (context === null) throw new Error("useInvoices must be used within a InvoicesProvider"); if (!context) {
throw new Error("useInvoices must be used within a InvoicesProvider");
}
return context; return context;
}; };

View File

@ -1,11 +1,4 @@
import { IListResponseDTO } from "@erp/core"; import { useDataSource, useQueryKey } from "@erp/core/client";
import {
IGetListDataProviderParams,
UseListQueryResult,
useDataSource,
useList,
useQueryKey,
} from "@erp/core/client";
import { IListInvoicesResponseDTO } from "@erp/invoices/common/dto"; import { IListInvoicesResponseDTO } from "@erp/invoices/common/dto";
export type UseInvoicesListParams = Omit<IGetListDataProviderParams, "filters" | "resource"> & { export type UseInvoicesListParams = Omit<IGetListDataProviderParams, "filters" | "resource"> & {

View File

@ -1,6 +1,6 @@
//import { ProtectedRoute } from "@erp/auth/components"; import { ModuleClientParams } from "@erp/core/client";
import { JSX, lazy } from "react"; import { lazy } from "react";
import { Route } from "react-router-dom"; import { Outlet, RouteObject } from "react-router-dom";
// Lazy load components // Lazy load components
const InvoicesLayout = lazy(() => const InvoicesLayout = lazy(() =>
@ -27,24 +27,32 @@ const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.Dashb
const InvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.InvoicesLayout }))); const InvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.InvoicesLayout })));
const InvoicesList = lazy(() => import("./app").then((m) => ({ default: m.InvoicesList })));*/ const InvoicesList = lazy(() => import("./app").then((m) => ({ default: m.InvoicesList })));*/
export const InvoiceRoutes = (): JSX.Element => { export const InvoiceRoutes = (params: ModuleClientParams): RouteObject[] => {
return ( return [
<Route path='/invoices' element={<InvoicesLayout />}> {
<Route index element={<InvoicesList />} /> path: "*",
<Route path='list' element={<InvoicesList />} /> element: (
<InvoicesLayout>
<Outlet context={params} />
</InvoicesLayout>
),
children: [
{ path: "", element: <InvoicesList /> }, // index
{ path: "list", element: <InvoicesList /> },
{ path: "*", element: <InvoicesList /> },
{/*<Route path='create' element={<InvoiceCreate />} /> //
<Route path=':id' element={<InvoicesList />} /> /*{ path: "create", element: <InvoicesList /> },
<Route path=':id/edit' element={<InvoicesList />} /> { path: ":id", element: <InvoicesList /> },
<Route path=':id/delete' element={<InvoicesList />} /> { path: ":id/edit", element: <InvoicesList /> },
<Route path=':id/view' element={<InvoicesList />} /> { path: ":id/delete", element: <InvoicesList /> },
<Route path=':id/print' element={<InvoicesList />} /> { path: ":id/view", element: <InvoicesList /> },
<Route path=':id/email' element={<InvoicesList />} /> { path: ":id/print", element: <InvoicesList /> },
<Route path=':id/download' element={<InvoicesList />} /> { path: ":id/email", element: <InvoicesList /> },
<Route path=':id/duplicate' element={<InvoicesList />} /> { path: ":id/download", element: <InvoicesList /> },
<Route path=':id/preview' element={<InvoicesList />} />*/} { path: ":id/duplicate", element: <InvoicesList /> },
{ path: ":id/preview", element: <InvoicesList /> },*/
<Route path='*' element={<InvoicesList />} /> ],
</Route> },
); ];
}; };

View File

@ -1,4 +1,4 @@
import { IModuleClient } from "@erp/core/client"; import { IModuleClient, ModuleClientParams } from "@erp/core/client";
import i18next from "i18next"; import i18next from "i18next";
import enResources from "../common/locales/en.json"; import enResources from "../common/locales/en.json";
import esResources from "../common/locales/es.json"; import esResources from "../common/locales/es.json";
@ -7,18 +7,18 @@ import { InvoiceRoutes } from "./invoice-routes";
const MODULE_NAME = "invoices"; const MODULE_NAME = "invoices";
const MODULE_VERSION = "1.0.0"; const MODULE_VERSION = "1.0.0";
export const InvoicesModuleManifest: IModuleClient = { export const InvoicesModuleManifiest: IModuleClient = {
name: MODULE_NAME, name: MODULE_NAME,
version: MODULE_VERSION, version: MODULE_VERSION,
dependencies: ["auth"], dependencies: ["auth"],
protected: true, protected: true,
layout: "app", layout: "app",
routes: () => { routes: (params: ModuleClientParams) => {
i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true); i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true); i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
return InvoiceRoutes(); return InvoiceRoutes(params);
}, },
}; };
export default InvoicesModuleManifest; export default InvoicesModuleManifiest;

View File

@ -41,6 +41,9 @@ importers:
'@erp/core': '@erp/core':
specifier: workspace:* specifier: workspace:*
version: link:../../modules/core version: link:../../modules/core
'@erp/invoices':
specifier: workspace:*
version: link:../../modules/invoices
bcrypt: bcrypt:
specifier: ^5.1.1 specifier: ^5.1.1
version: 5.1.1 version: 5.1.1