Subida a producción como Acana
This commit is contained in:
parent
1783f630cf
commit
e8d8403ae1
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/factuges-server",
|
"name": "@erp/factuges-server",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup src/index.ts --config tsup.config.ts",
|
"build": "tsup src/index.ts --config tsup.config.ts",
|
||||||
|
|||||||
1
apps/web/.env.production
Normal file
1
apps/web/.env.production
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_API_SERVER_URL=http://192.168.0.104:3002/api/v1
|
||||||
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/factuges-web",
|
"name": "@erp/factuges-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host --clearScreen false",
|
"dev": "vite --host --clearScreen false",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:rodax": "tsc && vite build --mode rodax",
|
"build:rodax": "tsc && vite build --mode rodax",
|
||||||
"build:acana": "tsc && vite build --mode acana",
|
"build:acana": "tsc && vite build --mode acana",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview --host",
|
||||||
"clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo",
|
"clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo",
|
||||||
"check:deps": "pnpm exec depcheck",
|
"check:deps": "pnpm exec depcheck",
|
||||||
"lint": "biome lint --fix",
|
"lint": "biome lint --fix",
|
||||||
@ -32,14 +32,13 @@
|
|||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
"@erp/customer-invoices": "workspace:*",
|
"@erp/customer-invoices": "workspace:*",
|
||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-ui": "workspace:*",
|
"@repo/rdx-ui": "workspace:*",
|
||||||
"@repo/shadcn-ui": "workspace:*",
|
"@repo/shadcn-ui": "workspace:*",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tanstack/react-query": "^5.90.6",
|
"@tanstack/react-query": "^5.90.6",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"i18next": "^25.0.2",
|
|
||||||
"i18next-browser-languagedetector": "^8.1.0",
|
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-hook-form": "^7.56.4",
|
"react-hook-form": "^7.56.4",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
|
|||||||
@ -10,16 +10,18 @@ import { Suspense } from "react";
|
|||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
|
|
||||||
import { i18n } from "@/locales";
|
|
||||||
|
|
||||||
import { clearAccessToken, getAccessToken, setAccessToken } from "./lib";
|
import { clearAccessToken, getAccessToken, setAccessToken } from "./lib";
|
||||||
import { getAppRouter } from "./routes";
|
import { getAppRouter } from "./routes";
|
||||||
|
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
||||||
|
import { initI18Next } from "@repo/i18next";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
DineroFactory.globalLocale = "es-ES";
|
DineroFactory.globalLocale = "es-ES";
|
||||||
|
|
||||||
|
const i18n = initI18Next();
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
@ -29,6 +31,8 @@ export const App = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("import.meta.env.VITE_API_SERVER_URL => ", import.meta.env.VITE_API_SERVER_URL);
|
||||||
|
|
||||||
const axiosInstance = createAxiosInstance({
|
const axiosInstance = createAxiosInstance({
|
||||||
baseURL: import.meta.env.VITE_API_SERVER_URL,
|
baseURL: import.meta.env.VITE_API_SERVER_URL,
|
||||||
getAccessToken: () => null, //getAccessToken,
|
getAccessToken: () => null, //getAccessToken,
|
||||||
|
|||||||
@ -1,34 +1,34 @@
|
|||||||
import i18n from "i18next";
|
import { initI18Next, registerTranslations } from "@repo/i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
|
export const i18n = await initI18Next();
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import enResources from "./en.json";
|
import enResources from "./en.json";
|
||||||
import esResources from "./es.json";
|
import esResources from "./es.json";
|
||||||
|
|
||||||
import enUIResources from "@repo/rdx-ui/locales/en.json";
|
const APP_MODULE_NAME = "FACTUGES_WEB";
|
||||||
import esUIResources from "@repo/rdx-ui/locales/es.json";
|
|
||||||
|
|
||||||
i18n
|
const ensureAppTranslations = () => {
|
||||||
// detect user language
|
registerTranslations(APP_MODULE_NAME, "es", esResources);
|
||||||
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
registerTranslations(APP_MODULE_NAME, "en", enResources);
|
||||||
.use(LanguageDetector)
|
};
|
||||||
// pass the i18n instance to react-i18next.
|
|
||||||
.use(initReactI18next)
|
|
||||||
// init i18next
|
|
||||||
// for all options read: https://www.i18next.com/overview/configuration-options
|
|
||||||
.init({
|
|
||||||
detection: {
|
|
||||||
order: ["navigator"],
|
|
||||||
},
|
|
||||||
debug: false, //import.meta.env.DEV,
|
|
||||||
fallbackLng: "es",
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false,
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
en: { ...enResources, ...enUIResources },
|
|
||||||
es: { ...esResources, ...esUIResources },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { i18n };
|
/**
|
||||||
|
* Hook de traducción específico de la app principal.
|
||||||
|
*/
|
||||||
|
export const useAppTranslation = () => {
|
||||||
|
// Hook base, sin namespace
|
||||||
|
const base = useI18NextTranslation();
|
||||||
|
const { i18n } = base;
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
// idempotente: solo añade si faltan bundles
|
||||||
|
ensureAppTranslations();
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
// Hook con namespace de la app
|
||||||
|
return useI18NextTranslation(APP_MODULE_NAME);
|
||||||
|
};
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
"files": [],
|
"files": [],
|
||||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
|
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/auth",
|
"name": "@erp/auth",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
@ -32,7 +32,6 @@
|
|||||||
"@repo/rdx-ui": "workspace:*",
|
"@repo/rdx-ui": "workspace:*",
|
||||||
"@repo/shadcn-ui": "workspace:*",
|
"@repo/shadcn-ui": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.90.6",
|
"@tanstack/react-query": "^5.90.6",
|
||||||
"i18next": "^25.1.1",
|
|
||||||
"react-hook-form": "^7.56.2",
|
"react-hook-form": "^7.56.2",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"react-secure-storage": "^1.3.2"
|
"react-secure-storage": "^1.3.2"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function mockUser(req: RequestWithAuth, _res: Response, next: NextFunctio
|
|||||||
userId: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data,
|
userId: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data,
|
||||||
email: EmailAddress.create("dev@example.com").data,
|
email: EmailAddress.create("dev@example.com").data,
|
||||||
companyId: UniqueID.create("019a9667-6a65-767a-a737-48234ee50a3a").data,
|
companyId: UniqueID.create("019a9667-6a65-767a-a737-48234ee50a3a").data,
|
||||||
companySlug: "acana",
|
companySlug: "alonsoysal",
|
||||||
roles: ["admin"],
|
roles: ["admin"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
import type { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
||||||
import i18next from "i18next";
|
|
||||||
import enResources from "../common/locales/en.json";
|
|
||||||
import esResources from "../common/locales/es.json";
|
|
||||||
import { AuthRoutes } from "./auth-routes";
|
import { AuthRoutes } from "./auth-routes";
|
||||||
|
|
||||||
|
//import enResources from "../common/locales/en.json";
|
||||||
|
//import esResources from "../common/locales/es.json";
|
||||||
|
|
||||||
const MODULE_NAME = "auth";
|
const MODULE_NAME = "auth";
|
||||||
const MODULE_VERSION = "1.0.0";
|
const MODULE_VERSION = "1.0.0";
|
||||||
|
|
||||||
@ -15,8 +16,8 @@ export const AuthModuleManifiest: IModuleClient = {
|
|||||||
layout: "auth",
|
layout: "auth",
|
||||||
|
|
||||||
routes: (params: ModuleClientParams) => {
|
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 AuthRoutes(params);
|
return AuthRoutes(params);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/core",
|
"name": "@erp/core",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
@ -45,7 +46,6 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"http-status": "^2.1.0",
|
"http-status": "^2.1.0",
|
||||||
"i18next": "^25.1.1",
|
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime-types": "^3.0.1",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
|
|||||||
@ -1,25 +1,37 @@
|
|||||||
import { i18n } from "i18next";
|
import { registerTranslations } from "@repo/i18next";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
import { useTranslation as useI18NextTranslation } from "react-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";
|
||||||
|
|
||||||
import { MODULE_NAME } from "./manifest";
|
import { MODULE_NAME } from "./manifest";
|
||||||
|
|
||||||
const addMissingBundles = (i18n: i18n) => {
|
/**
|
||||||
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
|
* Registra dinámicamente las traducciones del módulo.
|
||||||
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
|
*/
|
||||||
|
const ensureModuleTranslations = () => {
|
||||||
if (needsEn) {
|
registerTranslations(MODULE_NAME, "en", enResources);
|
||||||
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
registerTranslations(MODULE_NAME, "es", esResources);
|
||||||
}
|
|
||||||
|
|
||||||
if (needsEs) {
|
|
||||||
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook de traducción del módulo, version adaptada.
|
||||||
|
*
|
||||||
|
* - Asegura los bundles del módulo.
|
||||||
|
* - Devuelve el hook react-i18next con namespace.
|
||||||
|
*/
|
||||||
export const useTranslation = () => {
|
export const useTranslation = () => {
|
||||||
const { i18n } = useI18NextTranslation();
|
// Hook base, sin namespace
|
||||||
addMissingBundles(i18n);
|
const base = useI18NextTranslation();
|
||||||
|
const { i18n } = base;
|
||||||
|
|
||||||
|
// Asegura los bundles del módulo al montar (idempotente)
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
ensureModuleTranslations();
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
// Hook con namespace
|
||||||
return useI18NextTranslation(MODULE_NAME);
|
return useI18NextTranslation(MODULE_NAME);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/customer-invoices",
|
"name": "@erp/customer-invoices",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
@ -38,6 +38,7 @@
|
|||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
"@erp/customers": "workspace:*",
|
"@erp/customers": "workspace:*",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
@ -51,7 +52,6 @@
|
|||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"i18next": "^25.1.1",
|
|
||||||
"libphonenumber-js": "^1.12.7",
|
"libphonenumber-js": "^1.12.7",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
|
|||||||
@ -1,34 +1,37 @@
|
|||||||
import type { KeyPrefix, Namespace, i18n } from "i18next";
|
import { registerTranslations } from "@repo/i18next";
|
||||||
import {
|
import { useEffect } from "react";
|
||||||
type UseTranslationResponse,
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
useTranslation as useI18NextTranslation,
|
|
||||||
} from "react-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";
|
||||||
|
|
||||||
import { MODULE_NAME } from "./manifest";
|
import { MODULE_NAME } from "./manifest";
|
||||||
|
|
||||||
const addMissingBundles = (i18n: i18n) => {
|
/**
|
||||||
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
|
* Registra dinámicamente las traducciones del módulo.
|
||||||
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
|
*/
|
||||||
|
const ensureModuleTranslations = () => {
|
||||||
if (needsEn) {
|
registerTranslations(MODULE_NAME, "en", enResources);
|
||||||
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
registerTranslations(MODULE_NAME, "es", esResources);
|
||||||
}
|
|
||||||
|
|
||||||
if (needsEs) {
|
|
||||||
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTranslation = <
|
/**
|
||||||
Ns extends Namespace = typeof MODULE_NAME,
|
* Hook de traducción del módulo, version adaptada.
|
||||||
K extends KeyPrefix<Ns> = undefined,
|
*
|
||||||
>(
|
* - Asegura los bundles del módulo.
|
||||||
keyPrefix?: K
|
* - Devuelve el hook react-i18next con namespace.
|
||||||
): UseTranslationResponse<Ns, K> => {
|
*/
|
||||||
const { i18n } = useI18NextTranslation();
|
export const useTranslation = () => {
|
||||||
addMissingBundles(i18n);
|
// Hook base, sin namespace
|
||||||
return useI18NextTranslation(MODULE_NAME, { keyPrefix });
|
const base = useI18NextTranslation();
|
||||||
|
const { i18n } = base;
|
||||||
|
|
||||||
|
// Asegura los bundles del módulo al montar (idempotente)
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
ensureModuleTranslations();
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
// Hook con namespace
|
||||||
|
return useI18NextTranslation(MODULE_NAME);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { TaxCatalogProvider } from "@erp/core";
|
import type { TaxCatalogProvider } from "@erp/core";
|
||||||
import type { TFunction } from "i18next";
|
import type { ModuleTFunction } from "@repo/rdx-ui/locales/i18n.js";
|
||||||
import {
|
import {
|
||||||
type PropsWithChildren,
|
type PropsWithChildren,
|
||||||
createContext,
|
createContext,
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import type { MODULE_NAME } from "../../../../manifest";
|
|
||||||
|
|
||||||
export type ProformaContextValue = {
|
export type ProformaContextValue = {
|
||||||
company_id: string;
|
company_id: string;
|
||||||
@ -23,7 +22,7 @@ export type ProformaContextValue = {
|
|||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
taxCatalog: TaxCatalogProvider;
|
taxCatalog: TaxCatalogProvider;
|
||||||
|
|
||||||
t: TFunction<typeof MODULE_NAME>;
|
t: ModuleTFunction;
|
||||||
|
|
||||||
changeLanguage: (lang: string) => void;
|
changeLanguage: (lang: string) => void;
|
||||||
changeCurrency: (currency: string) => void;
|
changeCurrency: (currency: string) => void;
|
||||||
|
|||||||
BIN
modules/customer-invoices/templates/alonsoysal/factura_acana.jpg
Normal file
BIN
modules/customer-invoices/templates/alonsoysal/factura_acana.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@ -0,0 +1,351 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<title>Factura</title>
|
||||||
|
<style type="text/css">{{ asset 'tailwind.min.css' }}</style>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila superior */
|
||||||
|
.top-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloque izquierdo */
|
||||||
|
.left-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 80px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-text {
|
||||||
|
font-size: 7pt;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding-left: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloque derecho */
|
||||||
|
.right-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* uno encima de otro */
|
||||||
|
align-items: flex-end;
|
||||||
|
/* o flex-start / center según quieras */
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 25%;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factura-img {
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factura-text {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila inferior */
|
||||||
|
.bottom-header {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cuadros */
|
||||||
|
.info-box {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dire {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* ESTRUCTURA BODY */
|
||||||
|
/* ---------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 40px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 9pt;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anchos por columna */
|
||||||
|
.col-concepto { width: 75%; text-align: left; }
|
||||||
|
.col-cantidad { width: 5%; text-align: center;}
|
||||||
|
.col-precio { width: 10%; text-align: right;}
|
||||||
|
.col-total { width: 10%; text-align: right;}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
border-top: 0px solid;
|
||||||
|
border-left: 1px solid #000;
|
||||||
|
border-right: 1px solid #000;
|
||||||
|
border-bottom: 0px solid;
|
||||||
|
padding: 3px 10px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals td.label {
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 9pt;
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Columna izquierda (notas / forma de pago) */
|
||||||
|
.left-col {
|
||||||
|
width: 70%;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Etiquetas */
|
||||||
|
.resume-table .label {
|
||||||
|
width: 15%;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Valores numéricos */
|
||||||
|
.resume-table .value {
|
||||||
|
width: 15%;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Total factura */
|
||||||
|
.total-row .label,
|
||||||
|
.total-row .value {
|
||||||
|
background-color: #eee;
|
||||||
|
font-size: 9pt;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
color: #d10000;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-table .empty {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
* {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot {
|
||||||
|
display: table-footer-group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<!-- FILA SUPERIOR: logo + dirección / imagen factura -->
|
||||||
|
<div class="top-header">
|
||||||
|
<div class="left-block">
|
||||||
|
<div style="display: flex; align-items: center; gap: 4px; align-content: stretch">
|
||||||
|
<img src="{{asset 'logo_alonsoysal.jpg'}}" alt="Logo Alonso y Sal" class="logo" />
|
||||||
|
{{#if verifactu.qr_code}}
|
||||||
|
<div style="flex-grow: 0; flex-shrink: 0; flex-basis:90px;">
|
||||||
|
<img src="{{verifactu.qr_code}}" alt="QR factura" style="width: 90px; height: 90px;" />
|
||||||
|
</div>
|
||||||
|
<div style="text-align: left; flex-grow: 1; flex-shrink: 1; flex-basis: auto;">
|
||||||
|
QR tributario factura verificable en sede electronica de AEAT VERI*FACTU
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-block">
|
||||||
|
<div>
|
||||||
|
<div class="company-text">
|
||||||
|
<p><strong>Cocinas y Baños</strong><br/>Calle Vía Carpetana 340<br/>28047 Madrid</p>
|
||||||
|
<p>Teléfono: 914 652 842<br/>WhatsApp: 607 528 495<br/>Email: <a href="mailto:info@alonsoysal.com">info@alonsoysal.com</a><br/>Web: <a href="https://www.acanainteriorismo.com" target="_blank">www.alonsoysal.com</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FILA INFERIOR: cuadro factura + cuadro cliente -->
|
||||||
|
<div class="bottom-header">
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<p class="factura-text">FACTURA</p>
|
||||||
|
<p>Factura nº: {{series}}{{invoice_number}}</p>
|
||||||
|
<p>Fecha: {{invoice_date}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box info-dire">
|
||||||
|
<h2 style="font-weight:600; text-transform:uppercase; margin-bottom:0.25rem;">{{recipient.name}}</h2>
|
||||||
|
<p>{{recipient.tin}}</p>
|
||||||
|
<p>{{recipient.street}}</p>
|
||||||
|
<p>{{recipient.postal_code}} {{recipient.city}} {{recipient.province}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<section id="details">
|
||||||
|
|
||||||
|
<!-- Tu tabla -->
|
||||||
|
<table class="table-header">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-concepto">Concepto</th>
|
||||||
|
<th class="col-cantidad">Cantidad</th>
|
||||||
|
<th class="col-precio">Precio unidad</th>
|
||||||
|
<th class="col-total">Importe total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{description}}</td>
|
||||||
|
<td style="width: 10px !important; text-align:right;">{{#if quantity}}{{quantity}}{{else}} {{/if}}</td>
|
||||||
|
<td style="text-align:right;">{{#if unit_amount}}{{unit_amount}}{{else}} {{/if}}</td>
|
||||||
|
<td style="text-align:right;">{{#if taxable_amount}}{{taxable_amount}}{{else}} {{/if}}</td>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<tr class="resume-table">
|
||||||
|
<!-- Columna izquierda: notas y forma de pago -->
|
||||||
|
<td class="left-col" rowspan="10">
|
||||||
|
{{#if payment_method}}
|
||||||
|
<p><strong>Forma de pago:</strong> {{payment_method}}</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if notes}}
|
||||||
|
<p style="margin-top:0.5rem;"><strong>Notas:</strong> {{notes}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<!-- Columna derecha: totales -->
|
||||||
|
{{#if discount_percentage}}
|
||||||
|
<td class="label">Importe neto</td>
|
||||||
|
<td class="value">{{subtotal_amount}}</td>
|
||||||
|
{{else}}
|
||||||
|
<td colspan="2" class="label">Base imponible</td>
|
||||||
|
<td colspan="2" class="value">{{taxable_amount}}</td>
|
||||||
|
{{/if}}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{{#if discount_percentage}}
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td class="label">Dto {{discount_percentage}}</td>
|
||||||
|
<td class="value">{{discount_amount.value}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td colspan="2" class="label">Base imponible</td>
|
||||||
|
<td colspan="2" class="value">{{taxable_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each taxes}}
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td class="label">{{tax_name}}</td>
|
||||||
|
<td class="value">{{taxes_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<tr class="total-row">
|
||||||
|
<td class="label"><strong>Total factura</strong></td>
|
||||||
|
<td colspan="2" class="value total"><strong>{{total_amount}}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<footer id="footer" style="margin-top:1rem; border-top:1px solid #000000;">
|
||||||
|
<aside style="margin-top: 1rem;">
|
||||||
|
<tfoot>
|
||||||
|
<p style="text-align: center;">Insc. en el Reg. Merc. de Madrid, Tomo 31.839, Libro 0, Folio 191, Sección 8, Hoja
|
||||||
|
M-572991
|
||||||
|
CIF: B86913910</p>
|
||||||
|
<p style="text-align: left; font-size: 6pt;">Información en protección de datos:<br />De conformidad con lo
|
||||||
|
dispuesto en el RGPD y LOPDGDD,
|
||||||
|
informamos que los datos personales serán tratados por
|
||||||
|
ALISO DESIGN S.L para cumplir con la obligación tributaria de emitir facturas. Podrá solicitar más
|
||||||
|
información, y ejercer sus derechos escribiendo a info@acanainteriorismo.com o mediante correo postal a la
|
||||||
|
dirección CALLE
|
||||||
|
LA FUNDICION 27 POL. IND. SANTA ANA (28522) RIVAS-VACIAMADRID, MADRID. Para el ejercicio de sus derechos, en
|
||||||
|
caso
|
||||||
|
de que sea necesario, se le solicitará documento que acredite su identidad. Si siente vulnerados sus derechos
|
||||||
|
puede presentar una reclamación ante la AEPD, en su web: www.aepd.es.</p>
|
||||||
|
</tfoot>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
344
modules/customer-invoices/templates/alonsoysal/proforma.hbs
Normal file
344
modules/customer-invoices/templates/alonsoysal/proforma.hbs
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="{{ asset 'tailwind.css' }}" />
|
||||||
|
|
||||||
|
<title>Factura proforma</title>
|
||||||
|
<style type="text/css">
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* ESTRUCTURA CABECERA */
|
||||||
|
/* ---------------------------- */
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila superior */
|
||||||
|
.top-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloque izquierdo */
|
||||||
|
.left-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 70px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-text {
|
||||||
|
font-size: 7pt;
|
||||||
|
line-height: 1;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloque derecho */
|
||||||
|
.right-block {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factura-img {
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fila inferior */
|
||||||
|
.bottom-header {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cuadros */
|
||||||
|
.info-box {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dire {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- */
|
||||||
|
/* ESTRUCTURA BODY */
|
||||||
|
/* ---------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
margin: 40px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 9pt;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
border-top: 0px solid;
|
||||||
|
border-left: 1px solid #000;
|
||||||
|
border-right: 1px solid #000;
|
||||||
|
border-bottom: 0px solid;
|
||||||
|
padding: 3px 10px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #e7e0df;
|
||||||
|
color: #ff0014;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals td.label {
|
||||||
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 9pt;
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Columna izquierda (notas / forma de pago) */
|
||||||
|
.left-col {
|
||||||
|
width: 70%;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Etiquetas */
|
||||||
|
.resume-table .label {
|
||||||
|
width: 15%;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Valores numéricos */
|
||||||
|
.resume-table .value {
|
||||||
|
width: 15%;
|
||||||
|
padding: 6px 8px;
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Total factura */
|
||||||
|
.total-row .label,
|
||||||
|
.total-row .value {
|
||||||
|
background-color: #eee;
|
||||||
|
font-size: 9pt;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
color: #d10000;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-table .empty {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
* {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot {
|
||||||
|
display: table-footer-group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<!-- FILA SUPERIOR: logo + dirección / imagen factura -->
|
||||||
|
<div class="top-header">
|
||||||
|
<div class="left-block">
|
||||||
|
<img src="{{asset 'logo_acana.jpg'}}" alt="Logo Acana" class="logo" />
|
||||||
|
|
||||||
|
<div class="company-text">
|
||||||
|
<p>Aliso Design S.L. B86913910</p>
|
||||||
|
<p>C/ La Fundición, 27. Pol. Santa Ana</p>
|
||||||
|
<p>Rivas Vaciamadrid 28522 Madrid</p>
|
||||||
|
<p>Telf: 91 301 65 57 / 91 301 65 58</p>
|
||||||
|
<p>
|
||||||
|
<a href="mailto:info@acanainteriorismo.com">info@acanainteriorismo.com</a> -
|
||||||
|
<a href="https://www.acanainteriorismo.com" target="_blank">www.acanainteriorismo.com</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-block">
|
||||||
|
<img src="{{asset 'factura_acana.jpg' }}" alt="Factura" class="factura-img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FILA INFERIOR: cuadro factura + cuadro cliente -->
|
||||||
|
<div class="bottom-header">
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<p>Factura nº: <strong>{{series}}{{invoice_number}}</strong></p>
|
||||||
|
<p>Fecha: <strong>{{invoice_date}}</strong></p>
|
||||||
|
<p>Página <span class="pageNumber"></span> de <span class="totalPages"></span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box info-dire">
|
||||||
|
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
|
||||||
|
<p>{{recipient.tin}}</p>
|
||||||
|
<p>{{recipient.street}}</p>
|
||||||
|
<p>{{recipient.postal_code}} {{recipient.city}} {{recipient.province}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<section id="details">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Tu tabla -->
|
||||||
|
<table class="table-header">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="py-2">Concepto</th>
|
||||||
|
<th class="py-2">Ud.</th>
|
||||||
|
<th class="py-2">Imp.</th>
|
||||||
|
<th class="py-2"> </th>
|
||||||
|
<th class="py-2">Imp. total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{description}}</td>
|
||||||
|
<td class="text-right">{{#if quantity}}{{quantity}}{{else}} {{/if}}</td>
|
||||||
|
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}} {{/if}}</td>
|
||||||
|
<td class="text-right">{{#if discount_percentage}}{{discount_percentage}}{{else}} {{/if}}</td>
|
||||||
|
<td class="text-right">{{#if taxable_amount}}{{taxable_amount}}{{else}} {{/if}}</td>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<tr class="resume-table">
|
||||||
|
<!-- Columna izquierda: notas y forma de pago -->
|
||||||
|
<td class="left-col" rowspan="10">
|
||||||
|
{{#if payment_method}}
|
||||||
|
<p><strong>Forma de pago:</strong> {{payment_method}}</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if notes}}
|
||||||
|
<p class="mt-2"><strong>Notas:</strong> {{notes}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<!-- Columna derecha: totales -->
|
||||||
|
{{#if discount_percentage}}
|
||||||
|
<td colspan="2" class="label">Importe neto</td>
|
||||||
|
<td colspan="2" class="value">{{subtotal_amount}}</td>
|
||||||
|
{{else}}
|
||||||
|
<td colspan="2" class="label">Base imponible</td>
|
||||||
|
<td colspan="2" class="value">{{taxable_amount}}</td>
|
||||||
|
{{/if}}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{{#if discount_percentage}}
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td colspan="2" class="label">Dto {{discount_percentage}}</td>
|
||||||
|
<td colspan="2" class="value">{{discount_amount.value}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td colspan="2" class="label">Base imponible</td>
|
||||||
|
<td colspan="2" class="value">{{taxable_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each taxes}}
|
||||||
|
<tr class="resume-table">
|
||||||
|
<td colspan="2" class="label">{{tax_name}}</td>
|
||||||
|
<td colspan="2" class="value">{{taxes_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<tr class="total-row">
|
||||||
|
<td colspan="2" class="label"><strong>Total factura</strong></td>
|
||||||
|
<td colspan="2" class="value total"><strong>{{total_amount}}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<footer id="footer" class="mt-4 border-t border-black">
|
||||||
|
<aside class="mt-4">
|
||||||
|
<tfoot>
|
||||||
|
<p class="text-center">Insc. en el Reg. Merc. de Madrid, Tomo 31.839, Libro 0, Folio 191, Sección 8, Hoja
|
||||||
|
M-572991
|
||||||
|
CIF: B86913910</p>
|
||||||
|
<p class="text-left" style="font-size: 6pt;">Información en protección de datos<br />De conformidad con lo
|
||||||
|
dispuesto en el RGPD y LOPDGDD,
|
||||||
|
informamos que los datos personales serán tratados por
|
||||||
|
ALISO DESIGN S.L para cumplir con la obligación tributaria de emitir facturas. Podrá solicitar más
|
||||||
|
información, y ejercer sus derechos escribiendo a info@acanainteriorismo.com o mediante correo postal a la
|
||||||
|
dirección CALLE
|
||||||
|
LA FUNDICION 27 POL. IND. SANTA ANA (28522) RIVAS-VACIAMADRID, MADRID. Para el ejercicio de sus derechos, en
|
||||||
|
caso
|
||||||
|
de que sea necesario, se le solicitará documento que acredite su identidad. Si siente vulnerados sus derechos
|
||||||
|
puede presentar una reclamación ante la AEPD, en su web: www.aepd.es.</p>
|
||||||
|
</tfoot>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
176011
modules/customer-invoices/templates/alonsoysal/tailwind.css
Normal file
176011
modules/customer-invoices/templates/alonsoysal/tailwind.css
Normal file
File diff suppressed because it is too large
Load Diff
1
modules/customer-invoices/templates/alonsoysal/tailwind.min.css
vendored
Normal file
1
modules/customer-invoices/templates/alonsoysal/tailwind.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/customers",
|
"name": "@erp/customers",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
@ -32,6 +32,7 @@
|
|||||||
"@erp/auth": "workspace:*",
|
"@erp/auth": "workspace:*",
|
||||||
"@erp/core": "workspace:*",
|
"@erp/core": "workspace:*",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
@ -41,7 +42,6 @@
|
|||||||
"@tanstack/react-query": "^5.90.6",
|
"@tanstack/react-query": "^5.90.6",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"i18next": "^25.6.0",
|
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
"react-data-table-component": "^7.7.0",
|
"react-data-table-component": "^7.7.0",
|
||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
|
|||||||
@ -1,25 +1,37 @@
|
|||||||
import { i18n } from "i18next";
|
import { registerTranslations } from "@repo/i18next";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
import { useTranslation as useI18NextTranslation } from "react-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";
|
||||||
|
|
||||||
import { MODULE_NAME } from "./manifest";
|
import { MODULE_NAME } from "./manifest";
|
||||||
|
|
||||||
const addMissingBundles = (i18n: i18n) => {
|
/**
|
||||||
const needsEn = !i18n.hasResourceBundle("en", MODULE_NAME);
|
* Registra dinámicamente las traducciones del módulo.
|
||||||
const needsEs = !i18n.hasResourceBundle("es", MODULE_NAME);
|
*/
|
||||||
|
const ensureModuleTranslations = () => {
|
||||||
if (needsEn) {
|
registerTranslations(MODULE_NAME, "en", enResources);
|
||||||
i18n.addResourceBundle("en", MODULE_NAME, enResources, true, true);
|
registerTranslations(MODULE_NAME, "es", esResources);
|
||||||
}
|
|
||||||
|
|
||||||
if (needsEs) {
|
|
||||||
i18n.addResourceBundle("es", MODULE_NAME, esResources, true, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook de traducción del módulo, version adaptada.
|
||||||
|
*
|
||||||
|
* - Asegura los bundles del módulo.
|
||||||
|
* - Devuelve el hook react-i18next con namespace.
|
||||||
|
*/
|
||||||
export const useTranslation = () => {
|
export const useTranslation = () => {
|
||||||
const { i18n } = useI18NextTranslation();
|
// Hook base, sin namespace
|
||||||
addMissingBundles(i18n);
|
const base = useI18NextTranslation();
|
||||||
|
const { i18n } = base;
|
||||||
|
|
||||||
|
// Asegura los bundles del módulo al montar (idempotente)
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
ensureModuleTranslations();
|
||||||
|
}, [i18n]);
|
||||||
|
|
||||||
|
// Hook con namespace
|
||||||
return useI18NextTranslation(MODULE_NAME);
|
return useI18NextTranslation(MODULE_NAME);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@erp/doc-numbering",
|
"name": "@erp/doc-numbering",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
"ui:add": "pnpm --filter @repo/shadcn-ui ui:add",
|
"ui:add": "pnpm --filter @repo/shadcn-ui ui:add",
|
||||||
"create:package": "ts-node scripts/create-package.ts",
|
"create:package": "ts-node scripts/create-package.ts",
|
||||||
"volta:install": "curl https://get.volta.sh | bash",
|
"volta:install": "curl https://get.volta.sh | bash",
|
||||||
"clean": "turbo run clean && rimraf ./node_modules && rimraf ./package-lock.json"
|
"clean": "turbo run clean && rimraf ./node_modules && rimraf ./package-lock.json && rimraf ./out"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.3.1",
|
"@biomejs/biome": "2.3.1",
|
||||||
|
|||||||
25
packages/i18n/package.json
Normal file
25
packages/i18n/package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@repo/i18next",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
|
"clean": "rimraf .turbo node_modules dist"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"i18next": "25.6.0",
|
||||||
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"react-i18next": "^12.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@repo/typescript-config": "workspace:*",
|
||||||
|
"@types/node": "^22.15.12",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
57
packages/i18n/src/i18n.ts
Normal file
57
packages/i18n/src/i18n.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import i18next, { type i18n } from "i18next";
|
||||||
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
|
||||||
|
let hasInit = false;
|
||||||
|
|
||||||
|
export const initI18Next = () => {
|
||||||
|
if (hasInit) return i18next;
|
||||||
|
|
||||||
|
i18next
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
detection: { order: ["navigator"] },
|
||||||
|
debug: false,
|
||||||
|
fallbackLng: "es",
|
||||||
|
interpolation: { escapeValue: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
hasInit = true;
|
||||||
|
return i18next;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra dinámicamente traducciones de un módulo.
|
||||||
|
*
|
||||||
|
* Cada módulo tendrá su propio namespace.
|
||||||
|
*
|
||||||
|
* idempotente: si el bundle ya existe, no se vuelve a añadir.
|
||||||
|
*/
|
||||||
|
export const registerTranslations = (
|
||||||
|
moduleName: string,
|
||||||
|
locale: string,
|
||||||
|
resources: Record<string, unknown>
|
||||||
|
): void => {
|
||||||
|
if (!hasInit) {
|
||||||
|
throw new Error("i18n not initialized. Call initI18Next() first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ns = moduleName;
|
||||||
|
|
||||||
|
const alreadyExists = i18next.hasResourceBundle(locale, ns);
|
||||||
|
|
||||||
|
if (!alreadyExists) {
|
||||||
|
i18next.addResourceBundle(locale, ns, resources, true, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acceso al `t()` global por si se necesita en librerías de backend.
|
||||||
|
*/
|
||||||
|
export const t = (...args: Parameters<i18n["t"]>) => {
|
||||||
|
if (!hasInit) {
|
||||||
|
throw new Error("i18n not initialized. Call initI18Next() first.");
|
||||||
|
}
|
||||||
|
return i18next.t(...args);
|
||||||
|
};
|
||||||
1
packages/i18n/src/index.ts
Normal file
1
packages/i18n/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./i18n";
|
||||||
8
packages/i18n/tsconfig.json
Normal file
8
packages/i18n/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@repo/typescript-config/buildless.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-criteria",
|
"name": "@repo/rdx-criteria",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-ddd",
|
"name": "@repo/rdx-ddd",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-logger",
|
"name": "@repo/rdx-logger",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -47,6 +47,7 @@
|
|||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/shadcn-ui": "workspace:*",
|
"@repo/shadcn-ui": "workspace:*",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { t } from "i18next";
|
|
||||||
import { ChevronLeftIcon } from "lucide-react";
|
import { ChevronLeftIcon } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
export const BackHistoryButton = () => {
|
export const BackHistoryButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant='outline' size='icon' className='h-7 w-7' onClick={() => navigate(-1)}>
|
<Button className="h-7 w-7" onClick={() => navigate(-1)} size="icon" variant="outline">
|
||||||
<ChevronLeftIcon className='w-4 h-4' />
|
<ChevronLeftIcon className="w-4 h-4" />
|
||||||
<span className='sr-only'>{t("common.back")}</span>
|
<span className="sr-only">{t("common.back")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
import { t } from "i18next";
|
|
||||||
import { HelpCircleIcon } from "lucide-react";
|
import { HelpCircleIcon } from "lucide-react";
|
||||||
import React from "react";
|
import type React from "react";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
interface HelpButtonProps {
|
interface HelpButtonProps {
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
@ -27,24 +27,25 @@ export const HelpButton = ({
|
|||||||
content,
|
content,
|
||||||
className = "",
|
className = "",
|
||||||
}: HelpButtonProps) => {
|
}: HelpButtonProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-baseline justify-center mr-4 font-medium ${className}`}>
|
<div className={`flex items-baseline justify-center mr-4 font-medium ${className}`}>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant='link' className='inline-flex items-center font-medium group'>
|
<Button className="inline-flex items-center font-medium group" variant="link">
|
||||||
<span className='underline-offset-4 group-hover:underline'>{buttonText}</span>
|
<span className="underline-offset-4 group-hover:underline">{buttonText}</span>
|
||||||
<HelpCircleIcon className='w-4 h-4 ml-1 text-muted-foreground' />
|
<HelpCircleIcon className="w-4 h-4 ml-1 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className='sm:max-w-[425px]'>
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<ScrollArea className='grid gap-4 py-2'>
|
<ScrollArea className="grid gap-4 py-2">
|
||||||
{content}
|
{content}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type='button'>{t("common.close")}</Button>
|
<Button type="button">{t("common.close")}</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@ -1,137 +0,0 @@
|
|||||||
import { Header, Table, flexRender } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Separator,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
import { t } from "i18next";
|
|
||||||
import { ArrowDownIcon, ArrowDownUpIcon, ArrowUpIcon, EyeOffIcon } from "lucide-react";
|
|
||||||
|
|
||||||
interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
|
|
||||||
table: Table<TData>;
|
|
||||||
header: Header<TData, TValue>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DataTableColumnHeader<TData, TValue>({
|
|
||||||
table,
|
|
||||||
header,
|
|
||||||
className,
|
|
||||||
}: DataTableColumnHeaderProps<TData, TValue>) {
|
|
||||||
if (!header.column.getCanSort()) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={cn("data-[state=open]:bg-accent tracking-wide text-ellipsis", className)}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{false && header.column.getCanResize() && (
|
|
||||||
<Separator
|
|
||||||
orientation='vertical'
|
|
||||||
className={cn(
|
|
||||||
"absolute top-0 h-full w-[5px] bg-black/10 cursor-col-resize",
|
|
||||||
table.options.columnResizeDirection,
|
|
||||||
header.column.getIsResizing() ? "bg-primary opacity-100" : ""
|
|
||||||
)}
|
|
||||||
{...{
|
|
||||||
onDoubleClick: () => header.column.resetSize(),
|
|
||||||
onMouseDown: header.getResizeHandler(),
|
|
||||||
onTouchStart: header.getResizeHandler(),
|
|
||||||
style: {
|
|
||||||
transform:
|
|
||||||
table.options.columnResizeMode === "onEnd" && header.column.getIsResizing()
|
|
||||||
? `translateX(${
|
|
||||||
(table.options.columnResizeDirection === "rtl" ? -1 : 1) *
|
|
||||||
(table.getState().columnSizingInfo.deltaOffset ?? 0)
|
|
||||||
}px)`
|
|
||||||
: "",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn("flex items-center space-x-2", className)}>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
aria-label={
|
|
||||||
header.column.getIsSorted() === "desc"
|
|
||||||
? t("common.sort_desc_description")
|
|
||||||
: header.column.getIsSorted() === "asc"
|
|
||||||
? t("common.sort_asc_description")
|
|
||||||
: t("sort_none_description")
|
|
||||||
}
|
|
||||||
size='sm'
|
|
||||||
variant='ghost'
|
|
||||||
className='-ml-3 h-8 data-[state=open]:bg-accent font-bold text-muted-foreground'
|
|
||||||
>
|
|
||||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
||||||
|
|
||||||
{header.column.getIsSorted() === "desc" ? (
|
|
||||||
<ArrowDownIcon className='w-4 h-4 ml-2' aria-hidden='true' />
|
|
||||||
) : header.column.getIsSorted() === "asc" ? (
|
|
||||||
<ArrowUpIcon className='w-4 h-4 ml-2' aria-hidden='true' />
|
|
||||||
) : (
|
|
||||||
<ArrowDownUpIcon
|
|
||||||
className='w-4 h-4 ml-2 text-muted-foreground/30'
|
|
||||||
aria-hidden='true'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align='start'>
|
|
||||||
{header.column.getCanSort() && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => header.column.toggleSorting(false)}
|
|
||||||
aria-label={t("common.sort_asc")}
|
|
||||||
>
|
|
||||||
<ArrowUpIcon
|
|
||||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
|
||||||
aria-hidden='true'
|
|
||||||
/>
|
|
||||||
{t("common.sort_asc")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => header.column.toggleSorting(true)}
|
|
||||||
aria-label={t("common.sort_desc")}
|
|
||||||
>
|
|
||||||
<ArrowDownIcon
|
|
||||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
|
||||||
aria-hidden='true'
|
|
||||||
/>
|
|
||||||
{t("common.sort_desc")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{header.column.getCanSort() && header.column.getCanHide() && <DropdownMenuSeparator />}
|
|
||||||
|
|
||||||
{header.column.getCanHide() && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => header.column.toggleVisibility(false)}
|
|
||||||
aria-label={t("Hide")}
|
|
||||||
>
|
|
||||||
<EyeOffIcon
|
|
||||||
className='mr-2 h-3.5 w-3.5 text-muted-foreground/70'
|
|
||||||
aria-hidden='true'
|
|
||||||
/>
|
|
||||||
{t("Hide")}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuShortcut,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@repo/shadcn-ui/components";
|
|
||||||
import { CellContext } from "@tanstack/react-table";
|
|
||||||
import { MoreVerticalIcon } from "lucide-react";
|
|
||||||
import { ReactElement } from "react";
|
|
||||||
|
|
||||||
export type DataTablaRowActionFunction<TData> = (
|
|
||||||
props: CellContext<TData, unknown>
|
|
||||||
) => DataTableRowActionDefinition<TData>[];
|
|
||||||
|
|
||||||
export type DataTableRowActionDefinition<TData> = {
|
|
||||||
label: string | "-";
|
|
||||||
icon?: ReactElement<any, any>;
|
|
||||||
shortcut?: string;
|
|
||||||
onClick?: (props: CellContext<TData, unknown>, e: React.BaseSyntheticEvent) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DataTableRowActionsProps<TData, _TValue = unknown> = {
|
|
||||||
className?: string;
|
|
||||||
actions?: DataTablaRowActionFunction<TData>;
|
|
||||||
rowContext: CellContext<TData, unknown>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DataTableRowActions<TData = any, TValue = unknown>({
|
|
||||||
actions,
|
|
||||||
rowContext,
|
|
||||||
}: DataTableRowActionsProps<TData, TValue>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size='icon' variant='outline' className='w-8 h-8'>
|
|
||||||
<MoreVerticalIcon className='h-3.5 w-3.5' />
|
|
||||||
<span className='sr-only'>{t("common.open_menu")}</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
|
|
||||||
<DropdownMenuContent align='end'>
|
|
||||||
<DropdownMenuLabel>{t("common.actions")} </DropdownMenuLabel>
|
|
||||||
{actions &&
|
|
||||||
actions(rowContext).map((action, index) => {
|
|
||||||
// Use a more stable key: for separators, combine 'separator' and index; for items, use label and index
|
|
||||||
if (action.label === "-") {
|
|
||||||
// Use a more stable key by combining a static string and the previous/next action label if possible
|
|
||||||
const prevLabel = actions(rowContext)[index - 1]?.label ?? "start";
|
|
||||||
const nextLabel = actions(rowContext)[index + 1]?.label ?? "end";
|
|
||||||
return <DropdownMenuSeparator key={`separator-${prevLabel}-${nextLabel}`} />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={`action-${typeof action.label === "string" ? action.label : "item"}-${index}`}
|
|
||||||
onClick={(event) => (action.onClick ? action.onClick(rowContext, event) : null)}
|
|
||||||
>
|
|
||||||
{action.icon && <>{action.icon}</>}
|
|
||||||
{action.label}
|
|
||||||
{action.shortcut && <DropdownMenuShortcut>{action.shortcut}</DropdownMenuShortcut>}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { useSortable } from "@dnd-kit/sortable";
|
|
||||||
import { Button } from "@repo/shadcn-ui/components";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
import { t } from "i18next";
|
|
||||||
import { GripVerticalIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export interface DataTableRowDragHandleCellProps {
|
|
||||||
rowId: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DataTableRowDragHandleCell = ({
|
|
||||||
rowId,
|
|
||||||
className,
|
|
||||||
}: DataTableRowDragHandleCellProps) => {
|
|
||||||
const { attributes, listeners, isDragging } = useSortable({
|
|
||||||
id: rowId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
size='icon'
|
|
||||||
variant='link'
|
|
||||||
className={cn(
|
|
||||||
isDragging ? "cursor-grabbing" : "cursor-grab",
|
|
||||||
"w-4 h-4 mt-2 text-ring hover:text-muted-foreground",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
>
|
|
||||||
<GripVerticalIcon className='w-4 h-4' />
|
|
||||||
<span className='sr-only'>{t("common.move_row")}</span>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from "./datatable-column-header.tsx";
|
|
||||||
export * from "./datatable-row-actions.tsx";
|
|
||||||
export * from "./datatable-row-drag-handle-cell.tsx";
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
|
||||||
import { useTranslation } from "../../locales/i18n.ts";
|
import { useTranslation } from "../../locales/i18n.ts";
|
||||||
|
|
||||||
import { LoadingSpinIcon } from "./loading-spin-icon.tsx";
|
import { LoadingSpinIcon } from "./loading-spin-icon.tsx";
|
||||||
|
|
||||||
export type LoadingIndicatorProps = {
|
export type LoadingIndicatorProps = {
|
||||||
@ -26,7 +28,7 @@ export const LoadingIndicator = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"flex flex-col items-center max-w-xs justify-center w-full h-full mx-auto"}>
|
<div className={"flex flex-col items-center max-w-xs justify-center w-full h-full mx-auto"}>
|
||||||
<LoadingSpinIcon size={12} className={loadingSpinClassName} />
|
<LoadingSpinIcon className={loadingSpinClassName} size={12} />
|
||||||
{/*<Spinner {...spinnerProps} />*/}
|
{/*<Spinner {...spinnerProps} />*/}
|
||||||
{title ? (
|
{title ? (
|
||||||
<h2
|
<h2
|
||||||
|
|||||||
@ -1,28 +1,37 @@
|
|||||||
|
import { registerTranslations } from "@repo/i18next";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
import { useTranslation as useI18NextTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { PACKAGE_NAME } from "../index.ts";
|
import { PACKAGE_NAME } from "../index.ts";
|
||||||
|
|
||||||
import enResources from "./en.json" with { type: "json" };
|
import enResources from "./en.json" with { type: "json" };
|
||||||
import esResources from "./es.json" with { type: "json" };
|
import esResources from "./es.json" with { type: "json" };
|
||||||
|
|
||||||
const addMissingBundles = (i18n: any) => {
|
/**
|
||||||
const needsEn = !i18n.hasResourceBundle("en", PACKAGE_NAME);
|
* Registra dinámicamente las traducciones del módulo.
|
||||||
const needsEs = !i18n.hasResourceBundle("es", PACKAGE_NAME);
|
*/
|
||||||
|
const ensureModuleTranslations = () => {
|
||||||
if (needsEn) {
|
registerTranslations(PACKAGE_NAME, "en", enResources);
|
||||||
i18n.addResourceBundle("en", PACKAGE_NAME, enResources, true, true);
|
registerTranslations(PACKAGE_NAME, "es", esResources);
|
||||||
}
|
|
||||||
|
|
||||||
if (needsEs) {
|
|
||||||
i18n.addResourceBundle("es", PACKAGE_NAME, esResources, true, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook de traducción del módulo, version adaptada.
|
||||||
|
*
|
||||||
|
* - Asegura los bundles del módulo.
|
||||||
|
* - Devuelve el hook react-i18next con namespace.
|
||||||
|
*/
|
||||||
export const useTranslation = () => {
|
export const useTranslation = () => {
|
||||||
const { i18n } = useI18NextTranslation();
|
// Hook base, sin namespace
|
||||||
|
const base = useI18NextTranslation();
|
||||||
|
const { i18n } = base;
|
||||||
|
|
||||||
|
// Asegura los bundles del módulo al montar (idempotente)
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addMissingBundles(i18n);
|
ensureModuleTranslations();
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
|
|
||||||
|
// Hook con namespace
|
||||||
return useI18NextTranslation(PACKAGE_NAME);
|
return useI18NextTranslation(PACKAGE_NAME);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/rdx-utils",
|
"name": "@repo/rdx-utils",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -207,6 +207,9 @@ importers:
|
|||||||
'@erp/customers':
|
'@erp/customers':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../modules/customers
|
version: link:../../modules/customers
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/i18n
|
||||||
'@repo/rdx-ui':
|
'@repo/rdx-ui':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-ui
|
version: link:../../packages/rdx-ui
|
||||||
@ -225,12 +228,6 @@ importers:
|
|||||||
dinero.js:
|
dinero.js:
|
||||||
specifier: ^1.9.1
|
specifier: ^1.9.1
|
||||||
version: 1.9.1
|
version: 1.9.1
|
||||||
i18next:
|
|
||||||
specifier: ^25.0.2
|
|
||||||
version: 25.6.0(typescript@5.8.3)
|
|
||||||
i18next-browser-languagedetector:
|
|
||||||
specifier: ^8.1.0
|
|
||||||
version: 8.2.0
|
|
||||||
react-error-boundary:
|
react-error-boundary:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0(react@19.2.0)
|
version: 6.0.0(react@19.2.0)
|
||||||
@ -310,9 +307,6 @@ importers:
|
|||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^5.90.6
|
specifier: ^5.90.6
|
||||||
version: 5.90.6(react@19.2.0)
|
version: 5.90.6(react@19.2.0)
|
||||||
i18next:
|
|
||||||
specifier: ^25.1.1
|
|
||||||
version: 25.6.0(typescript@5.9.3)
|
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.56.2
|
specifier: ^7.56.2
|
||||||
version: 7.66.0(react@19.2.0)
|
version: 7.66.0(react@19.2.0)
|
||||||
@ -350,6 +344,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/i18n
|
||||||
'@repo/rdx-criteria':
|
'@repo/rdx-criteria':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-criteria
|
version: link:../../packages/rdx-criteria
|
||||||
@ -386,9 +383,6 @@ importers:
|
|||||||
http-status:
|
http-status:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
i18next:
|
|
||||||
specifier: ^25.1.1
|
|
||||||
version: 25.6.0(typescript@5.9.3)
|
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.503.0
|
specifier: ^0.503.0
|
||||||
version: 0.503.0(react@19.2.0)
|
version: 0.503.0(react@19.2.0)
|
||||||
@ -459,6 +453,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/i18n
|
||||||
'@repo/rdx-criteria':
|
'@repo/rdx-criteria':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-criteria
|
version: link:../../packages/rdx-criteria
|
||||||
@ -498,9 +495,6 @@ importers:
|
|||||||
handlebars:
|
handlebars:
|
||||||
specifier: ^4.7.8
|
specifier: ^4.7.8
|
||||||
version: 4.7.8
|
version: 4.7.8
|
||||||
i18next:
|
|
||||||
specifier: ^25.1.1
|
|
||||||
version: 25.6.0(typescript@5.9.3)
|
|
||||||
libphonenumber-js:
|
libphonenumber-js:
|
||||||
specifier: ^1.12.7
|
specifier: ^1.12.7
|
||||||
version: 1.12.25
|
version: 1.12.25
|
||||||
@ -571,6 +565,9 @@ importers:
|
|||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
version: 5.2.2(react-hook-form@7.66.0(react@19.2.0))
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/i18n
|
||||||
'@repo/rdx-criteria':
|
'@repo/rdx-criteria':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-criteria
|
version: link:../../packages/rdx-criteria
|
||||||
@ -598,9 +595,6 @@ importers:
|
|||||||
express:
|
express:
|
||||||
specifier: ^4.18.2
|
specifier: ^4.18.2
|
||||||
version: 4.21.2
|
version: 4.21.2
|
||||||
i18next:
|
|
||||||
specifier: ^25.6.0
|
|
||||||
version: 25.6.0(typescript@5.9.3)
|
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.503.0
|
specifier: ^0.503.0
|
||||||
version: 0.503.0(react@19.2.0)
|
version: 0.503.0(react@19.2.0)
|
||||||
@ -679,6 +673,31 @@ importers:
|
|||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages/i18n:
|
||||||
|
dependencies:
|
||||||
|
i18next:
|
||||||
|
specifier: 25.6.0
|
||||||
|
version: 25.6.0(typescript@5.9.3)
|
||||||
|
i18next-browser-languagedetector:
|
||||||
|
specifier: ^8.1.0
|
||||||
|
version: 8.2.0
|
||||||
|
i18next-http-backend:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2
|
||||||
|
react-i18next:
|
||||||
|
specifier: ^12.3.1
|
||||||
|
version: 12.3.1(i18next@25.6.0(typescript@5.9.3))(react@19.2.0)
|
||||||
|
devDependencies:
|
||||||
|
'@repo/typescript-config':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../typescript-config
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.15.12
|
||||||
|
version: 22.19.0
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.9.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/rdx-criteria:
|
packages/rdx-criteria:
|
||||||
dependencies:
|
dependencies:
|
||||||
sequelize:
|
sequelize:
|
||||||
@ -759,6 +778,9 @@ importers:
|
|||||||
'@radix-ui/react-tabs':
|
'@radix-ui/react-tabs':
|
||||||
specifier: ^1.1.12
|
specifier: ^1.1.12
|
||||||
version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
'@repo/i18next':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../i18n
|
||||||
'@repo/shadcn-ui':
|
'@repo/shadcn-ui':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shadcn-ui
|
version: link:../shadcn-ui
|
||||||
@ -3340,6 +3362,9 @@ packages:
|
|||||||
create-require@1.1.1:
|
create-require@1.1.1:
|
||||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
|
|
||||||
|
cross-fetch@4.0.0:
|
||||||
|
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@ -4036,6 +4061,9 @@ packages:
|
|||||||
i18next-browser-languagedetector@8.2.0:
|
i18next-browser-languagedetector@8.2.0:
|
||||||
resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==}
|
resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==}
|
||||||
|
|
||||||
|
i18next-http-backend@3.0.2:
|
||||||
|
resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==}
|
||||||
|
|
||||||
i18next@25.6.0:
|
i18next@25.6.0:
|
||||||
resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==}
|
resolution: {integrity: sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5107,6 +5135,19 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
||||||
|
react-i18next@12.3.1:
|
||||||
|
resolution: {integrity: sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==}
|
||||||
|
peerDependencies:
|
||||||
|
i18next: '>= 19.0.0'
|
||||||
|
react: '>= 16.8.0'
|
||||||
|
react-dom: '*'
|
||||||
|
react-native: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-i18next@15.7.4:
|
react-i18next@15.7.4:
|
||||||
resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==}
|
resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -8425,6 +8466,12 @@ snapshots:
|
|||||||
|
|
||||||
create-require@1.1.1: {}
|
create-require@1.1.1: {}
|
||||||
|
|
||||||
|
cross-fetch@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@ -9188,6 +9235,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
|
|
||||||
|
i18next-http-backend@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
cross-fetch: 4.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
i18next@25.6.0(typescript@5.8.3):
|
i18next@25.6.0(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
@ -10276,6 +10329,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
|
||||||
|
react-i18next@12.3.1(i18next@25.6.0(typescript@5.9.3))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.4
|
||||||
|
html-parse-stringify: 3.0.1
|
||||||
|
i18next: 25.6.0(typescript@5.9.3)
|
||||||
|
react: 19.2.0
|
||||||
|
|
||||||
react-i18next@15.7.4(i18next@25.6.0(typescript@5.8.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.8.3):
|
react-i18next@15.7.4(i18next@25.6.0(typescript@5.8.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
email soporte@rodax-software.com
|
|
||||||
auto_https disable_redirects
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
https://presupuestos.uecko.com:13001 {
|
|
||||||
reverse_proxy backend:3001
|
|
||||||
encode gzip # Comprime las respuestas con gzip
|
|
||||||
}
|
|
||||||
|
|
||||||
:443 {
|
|
||||||
root * /srv
|
|
||||||
file_server
|
|
||||||
try_files {path} /index.html # Esto asegura que las rutas en React funcionen correctamente
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_VERSION="1.2.0"
|
SCRIPT_VERSION="1.2.1"
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# FACTUGES Build Script
|
# FACTUGES Build Script
|
||||||
@ -216,18 +216,19 @@ if [[ "$MODE" == "api" || "$MODE" == "all" ]]; then
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "📦 API manifest generado en ${OUT_API_DIR}/manifest-v${API_VERSION}-${DATE}.json"
|
echo "📦 API manifest generado en ${OUT_API_DIR}/manifest-v${API_VERSION}-${DATE}.json"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$LOAD" == true ]]; then
|
if [[ "$LOAD" == true ]]; then
|
||||||
echo "📥 Cargando imagen en producción vps-2.rodax-software.com..."
|
echo "📥 Cargando imagen en producción vps-2.rodax-software.com..."
|
||||||
[[ "$MODE" == "web" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_WEB_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
|
[[ "$MODE" == "web" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_WEB_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
|
||||||
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_API_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
|
[[ "$MODE" == "api" || "$MODE" == "all" ]] && scp -r -P 49152 ${OUT_API_DIR} rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
|
||||||
|
|
||||||
[[ "$MODE" == "api" || "$MODE" == "all" ]] && RESULT=$(ssh -p 49152 rodax@vps-2.rodax-software.com "docker load -i /opt/factuges/${COMPANY}/api/${TAR_FILE_LATEST}")
|
[[ "$MODE" == "api" || "$MODE" == "all" ]] && RESULT=$(ssh -p 49152 rodax@vps-2.rodax-software.com "docker load -i ${TAR_FILE_LATEST}")
|
||||||
[[ "$MODE" == "api" || "$MODE" == "all" ]] && echo $RESULT
|
[[ "$MODE" == "api" || "$MODE" == "all" ]] && echo $RESULT
|
||||||
#docker load -i "${TAR_FILE_V}"
|
#docker load -i "${TAR_FILE_V}"
|
||||||
echo "✅ Imagen cargada en producción"
|
echo "✅ Imagen cargada en producción"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# 3️⃣ Resumen
|
# 3️⃣ Resumen
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user