@codemind.ec/medusa-plugin-invoice 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,12 +4,20 @@ const adminSdk = require("@medusajs/admin-sdk");
4
4
  const ui = require("@medusajs/ui");
5
5
  const Medusa = require("@medusajs/js-sdk");
6
6
  const react = require("react");
7
+ const icons = require("@medusajs/icons");
7
8
  const reactQuery = require("@tanstack/react-query");
9
+ const reactRouterDom = require("react-router-dom");
8
10
  const reactHookForm = require("react-hook-form");
9
11
  const zod = require("@medusajs/framework/zod");
12
+ const CodeMirror = require("@uiw/react-codemirror");
13
+ const langHtml = require("@codemirror/lang-html");
14
+ const themeOneDark = require("@codemirror/theme-one-dark");
15
+ const Handlebars = require("handlebars");
10
16
  require("@medusajs/admin-shared");
11
17
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
12
18
  const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
19
+ const CodeMirror__default = /* @__PURE__ */ _interopDefault(CodeMirror);
20
+ const Handlebars__default = /* @__PURE__ */ _interopDefault(Handlebars);
13
21
  const sdk = new Medusa__default.default({
14
22
  baseUrl: "/",
15
23
  debug: false,
@@ -64,17 +72,94 @@ const OrderInvoiceWidget = ({ data: order }) => {
64
72
  adminSdk.defineWidgetConfig({
65
73
  zone: "order.details.side.before"
66
74
  });
75
+ const TYPE_LABELS = {
76
+ order_invoice: "Comprobante de Pedido",
77
+ quote_proforma: "Cotización Proforma"
78
+ };
79
+ const InvoiceTemplatesPage = () => {
80
+ const navigate = reactRouterDom.useNavigate();
81
+ const queryClient = reactQuery.useQueryClient();
82
+ const { data, isLoading } = reactQuery.useQuery({
83
+ queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
84
+ queryKey: ["invoice-templates"]
85
+ });
86
+ const deleteMutation = reactQuery.useMutation({
87
+ mutationFn: (id) => sdk.client.fetch(`/admin/invoice-templates/${id}`, { method: "DELETE" }),
88
+ onSuccess: () => {
89
+ queryClient.invalidateQueries({ queryKey: ["invoice-templates"] });
90
+ ui.toast.success("Plantilla eliminada");
91
+ },
92
+ onError: () => {
93
+ ui.toast.error("No se pudo eliminar la plantilla");
94
+ }
95
+ });
96
+ const templates = (data == null ? void 0 : data.invoice_templates) ?? [];
97
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
98
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }, children: [
99
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Plantillas de Documentos" }),
100
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8 }, children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "Configuración" }),
102
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/invoice-templates/new"), children: "+ Nueva Plantilla" })
103
+ ] })
104
+ ] }),
105
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) : templates.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: 40, color: "#6c757d" }, children: "No hay plantillas. Se crearán automáticamente al reiniciar el servidor." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
106
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
107
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Nombre" }),
108
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Slug" }),
109
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Tipo" }),
110
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Estado" }),
111
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actualizado" }),
112
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
113
+ ] }) }),
114
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: templates.map((tpl) => /* @__PURE__ */ jsxRuntime.jsxs(
115
+ ui.Table.Row,
116
+ {
117
+ onClick: () => navigate(`/invoice-templates/${tpl.id}`),
118
+ style: { cursor: "pointer" },
119
+ children: [
120
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: tpl.name }),
121
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 12 }, children: tpl.slug }) }),
122
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: TYPE_LABELS[tpl.type] ?? tpl.type }),
123
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: tpl.is_default ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "blue", children: "Por defecto" }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "grey", children: "Personalizada" }) }),
124
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Date(tpl.updated_at).toLocaleDateString("es-ES") }),
125
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx(
126
+ ui.Button,
127
+ {
128
+ variant: "danger",
129
+ size: "small",
130
+ disabled: tpl.is_default,
131
+ onClick: (e) => {
132
+ e.stopPropagation();
133
+ if (confirm("¿Eliminar esta plantilla?")) {
134
+ deleteMutation.mutate(tpl.id);
135
+ }
136
+ },
137
+ children: "Eliminar"
138
+ }
139
+ ) })
140
+ ]
141
+ },
142
+ tpl.id
143
+ )) })
144
+ ] })
145
+ ] });
146
+ };
147
+ const config$1 = adminSdk.defineRouteConfig({
148
+ label: "Plantillas PDF",
149
+ icon: icons.DocumentText
150
+ });
67
151
  zod.z.object({
68
152
  company_name: zod.z.string().optional(),
69
153
  company_ruc: zod.z.string().optional(),
70
154
  company_address: zod.z.string().optional(),
71
155
  company_phone: zod.z.string().optional(),
72
- company_email: zod.z.string().email().optional(),
73
- company_logo: zod.z.string().url().optional(),
156
+ company_email: zod.z.string().email().optional().or(zod.z.literal("")),
157
+ company_logo: zod.z.string().optional(),
74
158
  notes: zod.z.string().optional(),
75
159
  admin_notification_email: zod.z.string().email().optional().or(zod.z.literal(""))
76
160
  });
77
161
  const InvoiceConfigPage = () => {
162
+ const navigate = reactRouterDom.useNavigate();
78
163
  const { data, isLoading, refetch } = reactQuery.useQuery({
79
164
  queryFn: () => sdk.client.fetch("/admin/invoice-config"),
80
165
  queryKey: ["invoice-config"]
@@ -87,6 +172,9 @@ const InvoiceConfigPage = () => {
87
172
  onSuccess: () => {
88
173
  refetch();
89
174
  ui.toast.success("Configuración actualizada exitosamente");
175
+ },
176
+ onError: () => {
177
+ ui.toast.error("Error al guardar la configuración");
90
178
  }
91
179
  });
92
180
  const getFormDefaultValues = react.useCallback(() => {
@@ -111,16 +199,23 @@ const InvoiceConfigPage = () => {
111
199
  if (!file) {
112
200
  return;
113
201
  }
114
- const { files } = await sdk.admin.upload.create({
115
- files: [file]
116
- });
117
- form.setValue("company_logo", files[0].url);
202
+ try {
203
+ const { files } = await sdk.admin.upload.create({
204
+ files: [file]
205
+ });
206
+ form.setValue("company_logo", files[0].url);
207
+ } catch (error) {
208
+ ui.toast.error("Error al subir el logo. Verifica que el archivo sea una imagen válida.");
209
+ }
118
210
  };
119
211
  react.useEffect(() => {
120
212
  form.reset(getFormDefaultValues());
121
213
  }, [getFormDefaultValues]);
122
214
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
123
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Configuración de Comprobante" }) }),
215
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Configuración de Comprobante" }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-templates"), children: "Gestionar Plantillas" })
218
+ ] }),
124
219
  /* @__PURE__ */ jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs(
125
220
  "form",
126
221
  {
@@ -249,6 +344,396 @@ const InvoiceConfigPage = () => {
249
344
  const config = adminSdk.defineRouteConfig({
250
345
  label: "Comprobante de Pedido"
251
346
  });
347
+ const NewTemplatePage = () => {
348
+ const navigate = reactRouterDom.useNavigate();
349
+ const [name, setName] = react.useState("");
350
+ const [slug, setSlug] = react.useState("");
351
+ const [type, setType] = react.useState("order_invoice");
352
+ const createMutation = reactQuery.useMutation({
353
+ mutationFn: (payload) => sdk.client.fetch("/admin/invoice-templates", {
354
+ method: "POST",
355
+ body: payload
356
+ }),
357
+ onSuccess: (data) => {
358
+ ui.toast.success("Plantilla creada");
359
+ navigate(`/invoice-templates/${data.invoice_template.id}`);
360
+ },
361
+ onError: () => {
362
+ ui.toast.error("Error al crear la plantilla");
363
+ }
364
+ });
365
+ const handleCreate = () => {
366
+ if (!name || !slug) {
367
+ ui.toast.error("Nombre y slug son requeridos");
368
+ return;
369
+ }
370
+ createMutation.mutate({
371
+ name,
372
+ slug,
373
+ type,
374
+ html_content: getDefaultHtml(type)
375
+ });
376
+ };
377
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
378
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", style: { marginBottom: 24 }, children: "Nueva Plantilla" }),
379
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 500 }, children: [
380
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "name", children: "Nombre" }),
382
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { id: "name", value: name, onChange: (e) => setName(e.target.value), placeholder: "Ej: Mi Comprobante" })
383
+ ] }),
384
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
385
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "slug", children: "Slug (identificador único)" }),
386
+ /* @__PURE__ */ jsxRuntime.jsx(
387
+ ui.Input,
388
+ {
389
+ id: "slug",
390
+ value: slug,
391
+ onChange: (e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
392
+ placeholder: "Ej: my_custom_invoice"
393
+ }
394
+ )
395
+ ] }),
396
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
397
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "type", children: "Tipo de documento" }),
398
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: type, onValueChange: setType, children: [
399
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Seleccionar tipo" }) }),
400
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
401
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "order_invoice", children: "Comprobante de Pedido" }),
402
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "quote_proforma", children: "Cotización Proforma" })
403
+ ] })
404
+ ] })
405
+ ] }),
406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, marginTop: 16 }, children: [
407
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-templates"), children: "Cancelar" }),
408
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleCreate, isLoading: createMutation.isPending, children: "Crear Plantilla" })
409
+ ] })
410
+ ] })
411
+ ] });
412
+ };
413
+ function getDefaultHtml(type) {
414
+ if (type === "quote_proforma") {
415
+ return `<!DOCTYPE html>
416
+ <html>
417
+ <head><style>body { font-family: Helvetica, sans-serif; }</style></head>
418
+ <body>
419
+ <h1>COTIZACIÓN {{quote_number}}</h1>
420
+ <p>Fecha: {{date_str}}</p>
421
+ <p>Servicio: {{service_name}}</p>
422
+ <!-- Personaliza tu plantilla aquí -->
423
+ </body>
424
+ </html>`;
425
+ }
426
+ return `<!DOCTYPE html>
427
+ <html>
428
+ <head><style>body { font-family: Helvetica, sans-serif; }</style></head>
429
+ <body>
430
+ <h1>COMPROBANTE {{invoice_id}}</h1>
431
+ <p>Fecha: {{invoice_date}}</p>
432
+ <p>Empresa: {{company_name}}</p>
433
+ <!-- Personaliza tu plantilla aquí -->
434
+ </body>
435
+ </html>`;
436
+ }
437
+ const VARIABLES = {
438
+ order_invoice: [
439
+ "company_name",
440
+ "company_ruc",
441
+ "company_address",
442
+ "company_phone",
443
+ "company_email",
444
+ "company_logo_base64",
445
+ "invoice_id",
446
+ "invoice_date",
447
+ "order_display_id",
448
+ "order_date",
449
+ "billing_address",
450
+ "shipping_address",
451
+ "cedula",
452
+ "subtotal",
453
+ "tax_total",
454
+ "shipping_total",
455
+ "discount_total",
456
+ "total",
457
+ "is_home_delivery",
458
+ "notes",
459
+ "{{#each items}}",
460
+ "this.title",
461
+ "this.variant_title",
462
+ "this.quantity",
463
+ "this.unit_price",
464
+ "this.total",
465
+ "{{/each}}"
466
+ ],
467
+ quote_proforma: [
468
+ "company_name",
469
+ "company_website",
470
+ "quote_number",
471
+ "date_str",
472
+ "service_name",
473
+ "is_home_delivery",
474
+ "{{#each config_fields}}",
475
+ "this.label",
476
+ "this.value",
477
+ "{{/each}}",
478
+ "{{#each breakdown}}",
479
+ "this.label",
480
+ "this.total_formatted",
481
+ "{{/each}}",
482
+ "totals.subtotal_formatted",
483
+ "totals.extras_formatted",
484
+ "totals.discount_formatted",
485
+ "totals.shipping_formatted",
486
+ "totals.taxes_provided",
487
+ "totals.taxes_formatted",
488
+ "totals.total_formatted",
489
+ "{{#each includes_left}}",
490
+ "{{#each includes_right}}",
491
+ "{{#each contact_rows}}",
492
+ "this.label",
493
+ "this.value",
494
+ "{{/each}}"
495
+ ]
496
+ };
497
+ const SAMPLE_DATA = {
498
+ order_invoice: {
499
+ company_name: "Mi Empresa",
500
+ company_ruc: "1234567890001",
501
+ company_address: "Av. Principal 123, Quito",
502
+ company_phone: "+593 99 123 4567",
503
+ company_email: "info@miempresa.com",
504
+ company_logo_base64: "",
505
+ invoice_id: "INV-000001",
506
+ invoice_date: "19/06/2025",
507
+ order_display_id: "000042",
508
+ order_date: "18/06/2025",
509
+ billing_address: "Juan Pérez\nCalle Ejemplo 456\nQuito, Pichincha",
510
+ shipping_address: "Juan Pérez\nCalle Ejemplo 456\nQuito, Pichincha",
511
+ cedula: "1712345678",
512
+ items: [
513
+ { title: "Producto ejemplo", variant_title: "Grande", quantity: "2", unit_price: "$25.00", total: "$50.00" },
514
+ { title: "Otro producto", variant_title: "", quantity: "1", unit_price: "$15.00", total: "$15.00" }
515
+ ],
516
+ subtotal: "$65.00",
517
+ tax_total: "$7.80",
518
+ shipping_total: "$5.00",
519
+ discount_total: "",
520
+ total: "$77.80",
521
+ is_home_delivery: false,
522
+ notes: "Gracias por su compra."
523
+ },
524
+ quote_proforma: {
525
+ company_name: "Mi Empresa",
526
+ company_website: "www.miempresa.com",
527
+ quote_number: "QP-2025-0001",
528
+ date_str: "19/06/2025",
529
+ service_name: "SERVICIO DE EJEMPLO",
530
+ config_fields: [
531
+ { label: "Nro. personas", value: "50" },
532
+ { label: "Menú", value: "Premium" }
533
+ ],
534
+ breakdown: [
535
+ { label: "Servicio base", total_formatted: "$500.00" },
536
+ { label: "Extras", total_formatted: "$100.00" }
537
+ ],
538
+ totals: {
539
+ subtotal_formatted: "$500.00",
540
+ extras_formatted: "$100.00",
541
+ discount: 0,
542
+ discount_formatted: "",
543
+ shipping: 25,
544
+ shipping_formatted: "$25.00",
545
+ taxes_provided: true,
546
+ taxes_formatted: "$75.00",
547
+ total_formatted: "700.00"
548
+ },
549
+ is_home_delivery: false,
550
+ includes: ["Vajilla", "Mantelería", "Personal", "Montaje"],
551
+ includes_left: ["Vajilla", "Personal"],
552
+ includes_right: ["Mantelería", "Montaje"],
553
+ contact_rows: [
554
+ { label: "Nombre", value: "María García" },
555
+ { label: "Email", value: "maria@ejemplo.com" }
556
+ ]
557
+ }
558
+ };
559
+ const TemplateEditorPage = () => {
560
+ const { id } = reactRouterDom.useParams();
561
+ const navigate = reactRouterDom.useNavigate();
562
+ const queryClient = reactQuery.useQueryClient();
563
+ const editorRef = react.useRef(null);
564
+ const { data, isLoading } = reactQuery.useQuery({
565
+ queryFn: () => sdk.client.fetch(`/admin/invoice-templates/${id}`),
566
+ queryKey: ["invoice-template", id],
567
+ enabled: !!id
568
+ });
569
+ const template = data == null ? void 0 : data.invoice_template;
570
+ const [name, setName] = react.useState("");
571
+ const [htmlContent, setHtmlContent] = react.useState("");
572
+ const [showVariables, setShowVariables] = react.useState(false);
573
+ react.useEffect(() => {
574
+ if (template) {
575
+ setName(template.name);
576
+ setHtmlContent(template.html_content);
577
+ }
578
+ }, [template]);
579
+ const saveMutation = reactQuery.useMutation({
580
+ mutationFn: (payload) => sdk.client.fetch(`/admin/invoice-templates/${id}`, {
581
+ method: "POST",
582
+ body: payload
583
+ }),
584
+ onSuccess: () => {
585
+ queryClient.invalidateQueries({ queryKey: ["invoice-template", id] });
586
+ ui.toast.success("Plantilla guardada");
587
+ },
588
+ onError: () => {
589
+ ui.toast.error("Error al guardar");
590
+ }
591
+ });
592
+ const handleSave = () => {
593
+ saveMutation.mutate({ name, html_content: htmlContent });
594
+ };
595
+ const handlePreviewPdf = async () => {
596
+ try {
597
+ const response = await fetch(`/admin/invoice-templates/${id}/preview`, {
598
+ method: "POST",
599
+ headers: { "Content-Type": "application/json" },
600
+ credentials: "include"
601
+ });
602
+ if (!response.ok) throw new Error("Preview failed");
603
+ const blob = await response.blob();
604
+ const url = URL.createObjectURL(blob);
605
+ window.open(url, "_blank");
606
+ } catch {
607
+ ui.toast.error("Error al generar preview PDF");
608
+ }
609
+ };
610
+ const handleRestoreDefault = async () => {
611
+ if (!(template == null ? void 0 : template.is_default)) return;
612
+ if (!confirm("¿Restaurar la plantilla a su contenido original?")) return;
613
+ try {
614
+ const res = await sdk.client.fetch(
615
+ `/admin/invoice-templates/${id}/restore`,
616
+ { method: "POST" }
617
+ );
618
+ ui.toast.success("Plantilla restaurada");
619
+ queryClient.invalidateQueries({ queryKey: ["invoice-template", id] });
620
+ } catch {
621
+ ui.toast.error("Error al restaurar. La función de restaurar aún no está implementada.");
622
+ }
623
+ };
624
+ const previewHtml = react.useMemo(() => {
625
+ if (!htmlContent || !template) return "";
626
+ try {
627
+ const compiled = Handlebars__default.default.compile(htmlContent);
628
+ return compiled(SAMPLE_DATA[template.type] ?? {});
629
+ } catch {
630
+ return `<div style="color:red;padding:20px;">Error en la plantilla Handlebars</div>`;
631
+ }
632
+ }, [htmlContent, template]);
633
+ const insertVariable = react.useCallback((variable) => {
634
+ const isBlock = variable.startsWith("{{#") || variable.startsWith("{{/");
635
+ const insertion = isBlock ? variable : `{{${variable}}}`;
636
+ setHtmlContent((prev) => prev + insertion);
637
+ }, []);
638
+ const availableVars = template ? VARIABLES[template.type] ?? [] : [];
639
+ if (isLoading) {
640
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) });
641
+ }
642
+ if (!template) {
643
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Plantilla no encontrada" }) });
644
+ }
645
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
646
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
647
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
648
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => navigate("/invoice-templates"), children: "← Volver" }),
649
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
650
+ ui.Input,
651
+ {
652
+ value: name,
653
+ onChange: (e) => setName(e.target.value),
654
+ style: { fontWeight: "bold", fontSize: 16 }
655
+ }
656
+ ) }),
657
+ /* @__PURE__ */ jsxRuntime.jsxs("code", { style: { fontSize: 12, color: "#6c757d" }, children: [
658
+ template.slug,
659
+ " (",
660
+ template.type,
661
+ ")"
662
+ ] })
663
+ ] }),
664
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8 }, children: [
665
+ template.is_default && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar Default" }),
666
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowVariables(!showVariables), children: showVariables ? "Ocultar Variables" : "Variables" }),
667
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handlePreviewPdf, children: "Preview PDF" }),
668
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: saveMutation.isPending, children: "Guardar" })
669
+ ] })
670
+ ] }),
671
+ showVariables && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
672
+ background: "#f8f9fa",
673
+ border: "1px solid #e2e8f0",
674
+ borderRadius: 8,
675
+ padding: 12,
676
+ marginBottom: 16,
677
+ display: "flex",
678
+ flexWrap: "wrap",
679
+ gap: 6
680
+ }, children: [
681
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { style: { width: "100%", marginBottom: 4 }, children: "Variables Handlebars — click para insertar:" }),
682
+ availableVars.map((v, i) => /* @__PURE__ */ jsxRuntime.jsx(
683
+ "button",
684
+ {
685
+ onClick: () => insertVariable(v),
686
+ style: {
687
+ background: v.startsWith("{{") ? "#e2e8f0" : "#dbeafe",
688
+ border: "1px solid #cbd5e0",
689
+ borderRadius: 4,
690
+ padding: "2px 8px",
691
+ fontSize: 11,
692
+ cursor: "pointer",
693
+ fontFamily: "monospace"
694
+ },
695
+ children: v.startsWith("{{") ? v : `{{${v}}}`
696
+ },
697
+ i
698
+ ))
699
+ ] }),
700
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)" }, children: [
701
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
702
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { style: { marginBottom: 8 }, children: "Editor HTML + Handlebars" }),
703
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, border: "1px solid #e2e8f0", borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(
704
+ CodeMirror__default.default,
705
+ {
706
+ ref: editorRef,
707
+ value: htmlContent,
708
+ onChange: (value) => setHtmlContent(value),
709
+ extensions: [langHtml.html()],
710
+ theme: themeOneDark.oneDark,
711
+ height: "100%",
712
+ style: { height: "100%" }
713
+ }
714
+ ) })
715
+ ] }),
716
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
717
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { style: { marginBottom: 8 }, children: "Preview en vivo (datos de ejemplo)" }),
718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
719
+ flex: 1,
720
+ border: "1px solid #e2e8f0",
721
+ borderRadius: 8,
722
+ overflow: "auto",
723
+ background: "#fff"
724
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(
725
+ "iframe",
726
+ {
727
+ srcDoc: previewHtml,
728
+ style: { width: "100%", height: "100%", border: "none" },
729
+ sandbox: "allow-same-origin",
730
+ title: "Template Preview"
731
+ }
732
+ ) })
733
+ ] })
734
+ ] })
735
+ ] });
736
+ };
252
737
  const i18nTranslations0 = {};
253
738
  const widgetModule = { widgets: [
254
739
  {
@@ -258,9 +743,21 @@ const widgetModule = { widgets: [
258
743
  ] };
259
744
  const routeModule = {
260
745
  routes: [
746
+ {
747
+ Component: InvoiceTemplatesPage,
748
+ path: "/invoice-templates"
749
+ },
261
750
  {
262
751
  Component: InvoiceConfigPage,
263
752
  path: "/invoice-config"
753
+ },
754
+ {
755
+ Component: NewTemplatePage,
756
+ path: "/invoice-templates/new"
757
+ },
758
+ {
759
+ Component: TemplateEditorPage,
760
+ path: "/invoice-templates/:id"
264
761
  }
265
762
  ]
266
763
  };
@@ -273,6 +770,14 @@ const menuItemModule = {
273
770
  nested: void 0,
274
771
  rank: void 0,
275
772
  translationNs: void 0
773
+ },
774
+ {
775
+ label: config$1.label,
776
+ icon: config$1.icon,
777
+ path: "/invoice-templates",
778
+ nested: void 0,
779
+ rank: void 0,
780
+ translationNs: void 0
276
781
  }
277
782
  ]
278
783
  };