diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index fe01b53a..03c1c74c 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -1,7 +1,8 @@ -import cors, { CorsOptions } from "cors"; -import express, { Application } from "express"; +import cors, { type CorsOptions } from "cors"; +import express, { type Application } from "express"; import helmet from "helmet"; import responseTime from "response-time"; + // ❗️ No cargamos dotenv aquí. Debe hacerse en el entrypoint o en ./config. // dotenv.config(); import { ENV } from "./config"; @@ -9,22 +10,6 @@ import { logger } from "./lib/logger"; export function createApp(): Application { const app = express(); - app.set("port", process.env.SERVER_PORT ?? 3002); - - // Oculta la cabecera x-powered-by - app.disable("x-powered-by"); - - // Desactiva ETag correctamente a nivel de Express - app.set("etag", false); - - // ─────────────────────────────────────────────────────────────────────────── - // Parsers - app.use(express.json()); - app.use(express.text()); - app.use(express.urlencoded({ extended: true })); - - // Métrica de tiempo de respuesta - app.use(responseTime()); // ─────────────────────────────────────────────────────────────────────────── // CORS @@ -60,6 +45,24 @@ export function createApp(): Application { }; app.use(cors(ENV.NODE_ENV === "development" ? devCors : prodCors)); + app.options("*", cors(ENV.NODE_ENV === "development" ? devCors : prodCors)); + + app.set("port", process.env.SERVER_PORT ?? 3002); + + // Oculta la cabecera x-powered-by + app.disable("x-powered-by"); + + // Desactiva ETag correctamente a nivel de Express + app.set("etag", false); + + // ─────────────────────────────────────────────────────────────────────────── + // Parsers + app.use(express.json()); + app.use(express.text()); + app.use(express.urlencoded({ extended: true })); + + // Métrica de tiempo de respuesta + app.use(responseTime()); // ─────────────────────────────────────────────────────────────────────────── // Seguridad HTTP diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 82bbcd73..b7c0aba6 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -248,7 +248,7 @@ process.on("uncaughtException", async (error: Error) => { logger.info("✅ Server is READY (readiness=true)"); logger.info(`startup_duration_ms=${DateTime.now().diff(currentState.launchedAt).toMillis()}`); - server.listen(currentState.port, () => { + server.listen(currentState.port, "0.0.0.0", () => { server.emit("listening"); const networkInterfaces = os.networkInterfaces(); diff --git a/modules/customer-invoices/src/common/locales/en.json b/modules/customer-invoices/src/common/locales/en.json index 773ca2d0..04a667f2 100644 --- a/modules/customer-invoices/src/common/locales/en.json +++ b/modules/customer-invoices/src/common/locales/en.json @@ -52,15 +52,46 @@ }, "issued_invoices": { "status": { - "all": "Todos", - "pendiente": "Pendiente", - "aceptado_con_error": "Aceptado con error", - "incorrecto": "Incorrecto", - "duplicado": "Duplicado", - "anulado": "Anulado", - "factura_inexistente": "Factura inexistente", - "rechazado": "Rechazado", - "error": "Error" + "all": { + "label": "All", + "description": "Proforma in preparation" + }, + "pendiente": { + "label": "Pending", + "description": "Queued record not yet processed" + }, + "correcto": { + "label": "Correct", + "description": "Record successfully processed by the AEAT" + }, + "aceptado_con_error": { + "label": "Accepted with errors", + "description": "Record accepted with errors by the AEAT. A correction record or a rectifying invoice is required" + }, + "incorrecto": { + "label": "Incorrect", + "description": "Record considered incorrect by the AEAT. A correction record with rechazo_previo=S or rechazo_previo=X, or a rectifying invoice, is required" + }, + "duplicado": { + "label": "Duplicate", + "description": "Record not accepted by the AEAT because another record with the same (series, number, issue_date) already exists" + }, + "anulado": { + "label": "Cancelled", + "description": "Cancellation record successfully processed by the AEAT" + }, + "factura_inexistente": { + "label": "Non-existent invoice", + "description": "Cancellation record not accepted by the AEAT because the invoice does not exist" + }, + "rechazado": { + "label": "Not registered", + "description": "Record rejected by the AEAT" + }, + "error": { + "label": "Error", + "description": "AEAT server error. The invoice record will be retried" + } } } }, diff --git a/modules/customer-invoices/src/common/locales/es.json b/modules/customer-invoices/src/common/locales/es.json index 74957266..c9c12770 100644 --- a/modules/customer-invoices/src/common/locales/es.json +++ b/modules/customer-invoices/src/common/locales/es.json @@ -52,15 +52,46 @@ }, "issued_invoices": { "status": { - "all": "All", - "pendiente": "Pendiente", - "aceptado_con_error": "Aceptado con error", - "incorrecto": "Incorrecto", - "duplicado": "Duplicado", - "anulado": "Anulado", - "factura_inexistente": "Factura inexistente", - "rechazado": "Rechazado", - "error": "Error" + "all": { + "label": "All", + "description": "Proforma en preparación" + }, + "pendiente": { + "label": "Pendiente", + "description": "Registro encolado y no procesado aún" + }, + "correcto": { + "label": "Correcto", + "description": "Registro procesado correctamente por la AEAT" + }, + "aceptado_con_error": { + "label": "Aceptado con errores", + "description": "Registro aceptado con errores por la AEAT. Se requiere enviar un registro de subsanación o emitir una rectificativa" + }, + "incorrecto": { + "label": "Incorrecto", + "description": "Registro considerado incorrecto por la AEAT. Se requiere enviar un registro de subsanación con rechazo_previo=S o rechazo_previo=X o emitir una rectificativa" + }, + "duplicado": { + "label": "Duplicado", + "description": "Registro no aceptado por la AEAT por existir un registro con el mismo (serie, numero, fecha_expedicion)" + }, + "anulado": { + "label": "Anulado", + "description": "Registro de anulación procesado correctamente por la AEAT" + }, + "factura_inexistente": { + "label": "Factura inexistente", + "description": "Registro de anulación no aceptado por la AEAT por no existir la factura" + }, + "rechazado": { + "label": "No registrado", + "description": "Registro rechazado por la AEAT" + }, + "error": { + "label": "Error", + "description": "Error en el servidor de la AEAT. Se intentará reenviar el registro de facturación de nuevo" + } } } }, diff --git a/modules/customer-invoices/src/web/issued-invoices/list/hooks/use-issued-invoice-list-query.ts b/modules/customer-invoices/src/web/issued-invoices/list/hooks/use-issued-invoice-list-query.ts index a7ec9c2f..1e3f34f0 100644 --- a/modules/customer-invoices/src/web/issued-invoices/list/hooks/use-issued-invoice-list-query.ts +++ b/modules/customer-invoices/src/web/issued-invoices/list/hooks/use-issued-invoice-list-query.ts @@ -33,6 +33,7 @@ export const useIssuedInvoiceListQuery = (options?: IssuedInvoicesQueryOptions) queryKey: ISSUED_INVOICES_QUERY_KEY(criteria), queryFn: async ({ signal }) => getIssuedInvoiceListApi(dataSource, signal, criteria), enabled, + staleTime: 5000, placeholderData: (previousData, _previousQuery) => previousData, // Mantener datos previos mientras se carga nueva datos (antiguo `keepPreviousData`) }); }; diff --git a/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx b/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx index ebe15950..c6f356e6 100644 --- a/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx +++ b/modules/customer-invoices/src/web/issued-invoices/list/ui/blocks/issued-invoices-grid/use-issued-invoices-grid-columns.tsx @@ -19,6 +19,7 @@ import QrCode from "react-qr-code"; import { useTranslation } from "../../../../../i18n"; import type { IssuedInvoiceSummaryData } from "../../../../types"; +import { VerifactuStatusBadge } from "../../components"; type GridActionHandlers = { onDownloadPdf?: (issuedInvoice: IssuedInvoiceSummaryData) => void; @@ -68,6 +69,15 @@ export function useIssuedInvoicesGridColumns( /> ), accessorFn: (row) => row.verifactu.status, // para ordenar/buscar por nombre + cell: ({ row }) => { + const invoice = row.original; + + return ( +
{t(`catalog.issued_invoices.status.${normalizedStatus.toLowerCase()}.description`)}
+