@codemind.ec/medusa-plugin-invoice 1.0.6 → 1.2.0

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.
@@ -1,13 +1,12 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Text, Button, toast, Label, Input, Textarea, Table, Badge, Select } from "@medusajs/ui";
3
+ import { Container, Heading, Text, Button, toast, Badge, Table, Label, Input, Textarea, Switch, Select } from "@medusajs/ui";
4
4
  import Medusa from "@medusajs/js-sdk";
5
5
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
6
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
7
- import { useForm, FormProvider, Controller } from "react-hook-form";
8
- import { z } from "@medusajs/framework/zod";
6
+ import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
9
7
  import { useNavigate, useParams } from "react-router-dom";
10
8
  import { DocumentText } from "@medusajs/icons";
9
+ import { useForm, FormProvider, Controller } from "react-hook-form";
11
10
  import CodeMirror from "@uiw/react-codemirror";
12
11
  import { html } from "@codemirror/lang-html";
13
12
  import { oneDark } from "@codemirror/theme-one-dark";
@@ -67,202 +66,167 @@ const OrderInvoiceWidget = ({ data: order }) => {
67
66
  defineWidgetConfig({
68
67
  zone: "order.details.side.before"
69
68
  });
70
- z.object({
71
- company_name: z.string().optional(),
72
- company_ruc: z.string().optional(),
73
- company_address: z.string().optional(),
74
- company_phone: z.string().optional(),
75
- company_email: z.string().email().optional().or(z.literal("")),
76
- company_logo: z.string().optional(),
77
- notes: z.string().optional(),
78
- admin_notification_email: z.string().email().optional().or(z.literal(""))
79
- });
80
69
  const InvoiceConfigPage = () => {
81
70
  const navigate = useNavigate();
82
- const { data, isLoading, refetch } = useQuery({
71
+ const { data: configData, isLoading: loadingConfigs } = useQuery({
83
72
  queryFn: () => sdk.client.fetch("/admin/invoice-config"),
84
- queryKey: ["invoice-config"]
73
+ queryKey: ["invoice-configs"]
85
74
  });
86
- const { mutateAsync, isPending } = useMutation({
87
- mutationFn: (payload) => sdk.client.fetch("/admin/invoice-config", {
88
- method: "POST",
89
- body: payload
90
- }),
75
+ const { data: templateData, isLoading: loadingTemplates } = useQuery({
76
+ queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
77
+ queryKey: ["invoice-templates"]
78
+ });
79
+ const configs = (configData == null ? void 0 : configData.invoice_configs) ?? [];
80
+ const templates = (templateData == null ? void 0 : templateData.invoice_templates) ?? [];
81
+ const defaultCompany = configs.find((c) => c.is_default);
82
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
83
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
84
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Comprobantes y Cotizaciones" }),
85
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mt-1", children: "Gestiona empresas emisoras y plantillas de documentos." })
86
+ ] }),
87
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 p-6", children: [
88
+ /* @__PURE__ */ jsxs("div", { className: "border border-ui-border-base rounded-lg p-5 flex flex-col gap-3", children: [
89
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
90
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Empresas" }),
91
+ /* @__PURE__ */ jsx(Badge, { color: configs.length > 0 ? "blue" : "grey", children: configs.length })
92
+ ] }),
93
+ loadingConfigs ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-sm", children: "Cargando..." }) : defaultCompany ? /* @__PURE__ */ jsxs("div", { className: "bg-ui-bg-subtle rounded-md p-3", children: [
94
+ /* @__PURE__ */ jsx(Text, { className: "text-sm font-medium", children: defaultCompany.company_name }),
95
+ defaultCompany.company_ruc && /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-xs", children: [
96
+ "RUC: ",
97
+ defaultCompany.company_ruc
98
+ ] }),
99
+ /* @__PURE__ */ jsx(Badge, { color: "green", className: "mt-1", children: "Default" })
100
+ ] }) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-sm", children: "No hay empresas configuradas." }),
101
+ /* @__PURE__ */ jsx(
102
+ Button,
103
+ {
104
+ variant: "secondary",
105
+ className: "mt-auto",
106
+ onClick: () => navigate("/invoice-config/companies"),
107
+ children: "Gestionar Empresas"
108
+ }
109
+ )
110
+ ] }),
111
+ /* @__PURE__ */ jsxs("div", { className: "border border-ui-border-base rounded-lg p-5 flex flex-col gap-3", children: [
112
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
113
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Plantillas" }),
114
+ /* @__PURE__ */ jsx(Badge, { color: templates.length > 0 ? "blue" : "grey", children: templates.length })
115
+ ] }),
116
+ loadingTemplates ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-sm", children: "Cargando..." }) : templates.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "bg-ui-bg-subtle rounded-md p-3 flex flex-col gap-1", children: [
117
+ templates.slice(0, 3).map((t) => /* @__PURE__ */ jsx(Text, { className: "text-sm", children: t.name }, t.id)),
118
+ templates.length > 3 && /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-xs", children: [
119
+ "+",
120
+ templates.length - 3,
121
+ " más..."
122
+ ] })
123
+ ] }) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-sm", children: "No hay plantillas. Se crearán al reiniciar el servidor." }),
124
+ /* @__PURE__ */ jsx(
125
+ Button,
126
+ {
127
+ variant: "secondary",
128
+ className: "mt-auto",
129
+ onClick: () => navigate("/invoice-config/invoice-templates"),
130
+ children: "Gestionar Plantillas"
131
+ }
132
+ )
133
+ ] })
134
+ ] })
135
+ ] });
136
+ };
137
+ const config$1 = defineRouteConfig({
138
+ label: "Comprobantes y Cotizaciones"
139
+ });
140
+ const CompaniesPage = () => {
141
+ const navigate = useNavigate();
142
+ const queryClient = useQueryClient();
143
+ const { data, isLoading } = useQuery({
144
+ queryFn: () => sdk.client.fetch("/admin/invoice-config"),
145
+ queryKey: ["invoice-configs"]
146
+ });
147
+ const setDefaultMutation = useMutation({
148
+ mutationFn: (id) => sdk.client.fetch(`/admin/invoice-config/${id}/set-default`, { method: "POST" }),
91
149
  onSuccess: () => {
92
- refetch();
93
- toast.success("Configuración actualizada exitosamente");
150
+ queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
151
+ toast.success("Empresa marcada como default");
94
152
  },
95
- onError: () => {
96
- toast.error("Error al guardar la configuración");
97
- }
153
+ onError: () => toast.error("Error al cambiar empresa default")
98
154
  });
99
- const getFormDefaultValues = useCallback(() => {
100
- return {
101
- company_name: (data == null ? void 0 : data.invoice_config.company_name) || "",
102
- company_ruc: (data == null ? void 0 : data.invoice_config.company_ruc) || "",
103
- company_address: (data == null ? void 0 : data.invoice_config.company_address) || "",
104
- company_phone: (data == null ? void 0 : data.invoice_config.company_phone) || "",
105
- company_email: (data == null ? void 0 : data.invoice_config.company_email) || "",
106
- company_logo: (data == null ? void 0 : data.invoice_config.company_logo) || "",
107
- notes: (data == null ? void 0 : data.invoice_config.notes) || "",
108
- admin_notification_email: (data == null ? void 0 : data.invoice_config.admin_notification_email) || ""
109
- };
110
- }, [data]);
111
- const form = useForm({
112
- defaultValues: getFormDefaultValues()
155
+ const deleteMutation = useMutation({
156
+ mutationFn: (id) => sdk.client.fetch(`/admin/invoice-config/${id}`, { method: "DELETE" }),
157
+ onSuccess: () => {
158
+ queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
159
+ toast.success("Empresa eliminada");
160
+ },
161
+ onError: () => toast.error("No se pudo eliminar la empresa")
113
162
  });
114
- const handleSubmit = form.handleSubmit((formData) => mutateAsync(formData));
115
- const uploadLogo = async (event) => {
116
- var _a;
117
- const file = (_a = event.target.files) == null ? void 0 : _a[0];
118
- if (!file) {
119
- return;
120
- }
121
- try {
122
- const { files } = await sdk.admin.upload.create({
123
- files: [file]
124
- });
125
- form.setValue("company_logo", files[0].url);
126
- } catch (error) {
127
- toast.error("Error al subir el logo. Verifica que el archivo sea una imagen válida.");
128
- }
129
- };
130
- useEffect(() => {
131
- form.reset(getFormDefaultValues());
132
- }, [getFormDefaultValues]);
163
+ const configs = (data == null ? void 0 : data.invoice_configs) ?? [];
133
164
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
134
165
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
135
- /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Configuración de Comprobante" }),
136
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config/invoice-templates"), children: "Gestionar Plantillas" })
166
+ /* @__PURE__ */ jsxs("div", { children: [
167
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Empresas" }),
168
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-sm mt-1", children: "Empresas emisoras de comprobantes y cotizaciones." })
169
+ ] }),
170
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
171
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "← Panel" }),
172
+ /* @__PURE__ */ jsx(Button, { onClick: () => navigate("/invoice-config/companies/new"), children: "+ Nueva Empresa" })
173
+ ] })
137
174
  ] }),
138
- /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs(
139
- "form",
140
- {
141
- onSubmit: handleSubmit,
142
- className: "flex h-full flex-col overflow-hidden p-2 gap-2",
143
- children: [
144
- /* @__PURE__ */ jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-3 mb-2", children: /* @__PURE__ */ jsx(
145
- Controller,
146
- {
147
- control: form.control,
148
- name: "admin_notification_email",
149
- render: ({ field }) => {
150
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
151
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }) }),
152
- /* @__PURE__ */ jsx(Input, { ...field, onChange: field.onChange, value: field.value, placeholder: "admin@mariquita.food" }),
153
- /* @__PURE__ */ jsx("span", { className: "text-ui-fg-subtle text-xs", children: "Se enviarán notificaciones de nuevos pedidos y comprobantes de pago a este email." })
154
- ] });
155
- }
156
- }
157
- ) }),
158
- /* @__PURE__ */ jsx(
159
- Controller,
160
- {
161
- control: form.control,
162
- name: "company_name",
163
- render: ({ field }) => {
164
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
165
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Nombre de la Empresa" }) }),
166
- /* @__PURE__ */ jsx(Input, { ...field, onChange: field.onChange, value: field.value })
167
- ] });
168
- }
169
- }
170
- ),
171
- /* @__PURE__ */ jsx(
172
- Controller,
173
- {
174
- control: form.control,
175
- name: "company_ruc",
176
- render: ({ field }) => {
177
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
178
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "RUC" }) }),
179
- /* @__PURE__ */ jsx(Input, { ...field, onChange: field.onChange, value: field.value, placeholder: "1234567890001" })
180
- ] });
181
- }
182
- }
183
- ),
184
- /* @__PURE__ */ jsx(
185
- Controller,
186
- {
187
- control: form.control,
188
- name: "company_address",
189
- render: ({ field }) => {
190
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
191
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Dirección de la Empresa" }) }),
192
- /* @__PURE__ */ jsx(Textarea, { ...field })
193
- ] });
194
- }
195
- }
196
- ),
197
- /* @__PURE__ */ jsx(
198
- Controller,
199
- {
200
- control: form.control,
201
- name: "company_phone",
202
- render: ({ field }) => {
203
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
204
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Teléfono de la Empresa" }) }),
205
- /* @__PURE__ */ jsx(Input, { ...field })
206
- ] });
207
- }
208
- }
209
- ),
210
- /* @__PURE__ */ jsx(
211
- Controller,
212
- {
213
- control: form.control,
214
- name: "company_email",
215
- render: ({ field }) => {
216
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
217
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Email de la Empresa" }) }),
218
- /* @__PURE__ */ jsx(Input, { ...field })
219
- ] });
220
- }
221
- }
222
- ),
223
- /* @__PURE__ */ jsx(
224
- Controller,
225
- {
226
- control: form.control,
227
- name: "notes",
228
- render: ({ field }) => {
229
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
230
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Notas" }) }),
231
- /* @__PURE__ */ jsx(Textarea, { ...field })
232
- ] });
233
- }
234
- }
235
- ),
236
- /* @__PURE__ */ jsx(
237
- Controller,
238
- {
239
- control: form.control,
240
- name: "company_logo",
241
- render: ({ field }) => {
242
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-2", children: [
243
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }) }),
244
- /* @__PURE__ */ jsx(Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
245
- field.value && /* @__PURE__ */ jsx(
246
- "img",
247
- {
248
- src: field.value,
249
- alt: "Logo de la Empresa",
250
- className: "mt-2 h-24 w-24"
175
+ /* @__PURE__ */ jsx("div", { className: "px-6 pb-4", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "Cargando..." }) : configs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "No hay empresas configuradas." }) : /* @__PURE__ */ jsxs(Table, { children: [
176
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
177
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Nombre" }),
178
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "RUC" }),
179
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Email" }),
180
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Teléfono" }),
181
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Estado" }),
182
+ /* @__PURE__ */ jsx(Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
183
+ ] }) }),
184
+ /* @__PURE__ */ jsx(Table.Body, { children: configs.map((cfg) => /* @__PURE__ */ jsxs(
185
+ Table.Row,
186
+ {
187
+ onClick: () => navigate(`/invoice-config/companies/${cfg.id}`),
188
+ style: { cursor: "pointer" },
189
+ children: [
190
+ /* @__PURE__ */ jsx(Table.Cell, { className: "font-medium", children: cfg.company_name }),
191
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("code", { className: "text-xs", children: cfg.company_ruc || "—" }) }),
192
+ /* @__PURE__ */ jsx(Table.Cell, { children: cfg.company_email }),
193
+ /* @__PURE__ */ jsx(Table.Cell, { children: cfg.company_phone }),
194
+ /* @__PURE__ */ jsx(Table.Cell, { children: cfg.is_default ? /* @__PURE__ */ jsx(Badge, { color: "green", children: "Default" }) : /* @__PURE__ */ jsx(Badge, { color: "grey", children: "—" }) }),
195
+ /* @__PURE__ */ jsx(Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsx("div", { className: "flex gap-2 justify-end", children: !cfg.is_default && /* @__PURE__ */ jsxs(Fragment, { children: [
196
+ /* @__PURE__ */ jsx(
197
+ Button,
198
+ {
199
+ variant: "secondary",
200
+ size: "small",
201
+ onClick: (e) => {
202
+ e.stopPropagation();
203
+ setDefaultMutation.mutate(cfg.id);
204
+ },
205
+ children: "Default"
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsx(
209
+ Button,
210
+ {
211
+ variant: "danger",
212
+ size: "small",
213
+ onClick: (e) => {
214
+ e.stopPropagation();
215
+ if (confirm("¿Eliminar esta empresa?")) {
216
+ deleteMutation.mutate(cfg.id);
251
217
  }
252
- )
253
- ] });
254
- }
255
- }
256
- ),
257
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isLoading || isPending, children: "Guardar" })
258
- ]
259
- }
260
- ) })
218
+ },
219
+ children: "Eliminar"
220
+ }
221
+ )
222
+ ] }) }) })
223
+ ]
224
+ },
225
+ cfg.id
226
+ )) })
227
+ ] }) })
261
228
  ] });
262
229
  };
263
- const config$1 = defineRouteConfig({
264
- label: "Comprobante de Pedido"
265
- });
266
230
  const TYPE_LABELS = {
267
231
  order_invoice: "Comprobante de Pedido",
268
232
  quote_proforma: "Cotización Proforma"
@@ -274,6 +238,10 @@ const InvoiceTemplatesPage = () => {
274
238
  queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
275
239
  queryKey: ["invoice-templates"]
276
240
  });
241
+ const { data: companiesData } = useQuery({
242
+ queryFn: () => sdk.client.fetch("/admin/invoice-config"),
243
+ queryKey: ["invoice-configs"]
244
+ });
277
245
  const deleteMutation = useMutation({
278
246
  mutationFn: (id) => sdk.client.fetch(`/admin/invoice-templates/${id}`, { method: "DELETE" }),
279
247
  onSuccess: () => {
@@ -285,19 +253,21 @@ const InvoiceTemplatesPage = () => {
285
253
  }
286
254
  });
287
255
  const templates = (data == null ? void 0 : data.invoice_templates) ?? [];
288
- return /* @__PURE__ */ jsxs(Container, { children: [
289
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }, children: [
256
+ const companiesMap = new Map(((companiesData == null ? void 0 : companiesData.invoice_configs) ?? []).map((c) => [c.id, c.company_name]));
257
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
290
259
  /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Plantillas de Documentos" }),
291
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
292
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "Configuración" }),
260
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
261
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "← Panel" }),
293
262
  /* @__PURE__ */ jsx(Button, { onClick: () => navigate("/invoice-config/invoice-templates/new"), children: "+ Nueva Plantilla" })
294
263
  ] })
295
264
  ] }),
296
- isLoading ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) : templates.length === 0 ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40, color: "#6c757d" }, children: "No hay plantillas. Se crearán automáticamente al reiniciar el servidor." }) : /* @__PURE__ */ jsxs(Table, { children: [
265
+ /* @__PURE__ */ jsx("div", { className: "px-6 pb-4", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "Cargando..." }) : templates.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "No hay plantillas. Se crearán automáticamente al reiniciar el servidor." }) : /* @__PURE__ */ jsxs(Table, { children: [
297
266
  /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
298
267
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Nombre" }),
299
268
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Slug" }),
300
269
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Tipo" }),
270
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Empresa" }),
301
271
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Estado" }),
302
272
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actualizado" }),
303
273
  /* @__PURE__ */ jsx(Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
@@ -308,9 +278,10 @@ const InvoiceTemplatesPage = () => {
308
278
  onClick: () => navigate(`/invoice-config/invoice-templates/${tpl.id}`),
309
279
  style: { cursor: "pointer" },
310
280
  children: [
311
- /* @__PURE__ */ jsx(Table.Cell, { children: tpl.name }),
312
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("code", { style: { fontSize: 12 }, children: tpl.slug }) }),
281
+ /* @__PURE__ */ jsx(Table.Cell, { className: "font-medium", children: tpl.name }),
282
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("code", { className: "text-xs", children: tpl.slug }) }),
313
283
  /* @__PURE__ */ jsx(Table.Cell, { children: TYPE_LABELS[tpl.type] ?? tpl.type }),
284
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "text-sm", children: tpl.company_id ? companiesMap.get(tpl.company_id) ?? "—" : "—" }) }),
314
285
  /* @__PURE__ */ jsx(Table.Cell, { children: tpl.is_default ? /* @__PURE__ */ jsx(Badge, { color: "blue", children: "Por defecto" }) : /* @__PURE__ */ jsx(Badge, { color: "grey", children: "Personalizada" }) }),
315
286
  /* @__PURE__ */ jsx(Table.Cell, { children: new Date(tpl.updated_at).toLocaleDateString("es-ES") }),
316
287
  /* @__PURE__ */ jsx(Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsx(
@@ -332,13 +303,356 @@ const InvoiceTemplatesPage = () => {
332
303
  },
333
304
  tpl.id
334
305
  )) })
335
- ] })
306
+ ] }) })
336
307
  ] });
337
308
  };
338
309
  const config = defineRouteConfig({
339
310
  label: "Plantillas PDF",
340
311
  icon: DocumentText
341
312
  });
313
+ const EditCompanyPage = () => {
314
+ const { id } = useParams();
315
+ const navigate = useNavigate();
316
+ const queryClient = useQueryClient();
317
+ const { data, isLoading } = useQuery({
318
+ queryFn: () => sdk.client.fetch(`/admin/invoice-config/${id}`),
319
+ queryKey: ["invoice-config", id],
320
+ enabled: !!id
321
+ });
322
+ const config2 = data == null ? void 0 : data.invoice_config;
323
+ const getDefaults = useCallback(() => ({
324
+ company_name: (config2 == null ? void 0 : config2.company_name) || "",
325
+ company_ruc: (config2 == null ? void 0 : config2.company_ruc) || "",
326
+ company_address: (config2 == null ? void 0 : config2.company_address) || "",
327
+ company_phone: (config2 == null ? void 0 : config2.company_phone) || "",
328
+ company_email: (config2 == null ? void 0 : config2.company_email) || "",
329
+ company_logo: (config2 == null ? void 0 : config2.company_logo) || "",
330
+ notes: (config2 == null ? void 0 : config2.notes) || "",
331
+ admin_notification_email: (config2 == null ? void 0 : config2.admin_notification_email) || "",
332
+ is_default: (config2 == null ? void 0 : config2.is_default) || false
333
+ }), [config2]);
334
+ const form = useForm({ defaultValues: getDefaults() });
335
+ useEffect(() => {
336
+ form.reset(getDefaults());
337
+ }, [getDefaults]);
338
+ const saveMutation = useMutation({
339
+ mutationFn: (payload) => sdk.client.fetch(`/admin/invoice-config/${id}`, {
340
+ method: "POST",
341
+ body: payload
342
+ }),
343
+ onSuccess: () => {
344
+ queryClient.invalidateQueries({ queryKey: ["invoice-config", id] });
345
+ queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
346
+ toast.success("Empresa actualizada");
347
+ },
348
+ onError: () => toast.error("Error al guardar")
349
+ });
350
+ const uploadLogo = async (event) => {
351
+ var _a;
352
+ const file = (_a = event.target.files) == null ? void 0 : _a[0];
353
+ if (!file) return;
354
+ try {
355
+ const { files } = await sdk.admin.upload.create({ files: [file] });
356
+ form.setValue("company_logo", files[0].url);
357
+ } catch {
358
+ toast.error("Error al subir el logo.");
359
+ }
360
+ };
361
+ const handleSubmit = form.handleSubmit((data2) => saveMutation.mutate(data2));
362
+ if (isLoading) {
363
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
364
+ }
365
+ if (!config2) {
366
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: "Empresa no encontrada" }) });
367
+ }
368
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
369
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
370
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Editar Empresa" }),
371
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "← Volver" })
372
+ ] }),
373
+ /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 p-6 max-w-2xl", children: [
374
+ /* @__PURE__ */ jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-4 mb-2", children: /* @__PURE__ */ jsx(
375
+ Controller,
376
+ {
377
+ control: form.control,
378
+ name: "admin_notification_email",
379
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
380
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }),
381
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "admin@empresa.com" }),
382
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Notificaciones de nuevos pedidos a este email." })
383
+ ] })
384
+ }
385
+ ) }),
386
+ /* @__PURE__ */ jsx(
387
+ Controller,
388
+ {
389
+ control: form.control,
390
+ name: "company_name",
391
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
392
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Nombre de la Empresa *" }),
393
+ /* @__PURE__ */ jsx(Input, { ...field })
394
+ ] })
395
+ }
396
+ ),
397
+ /* @__PURE__ */ jsx(
398
+ Controller,
399
+ {
400
+ control: form.control,
401
+ name: "company_ruc",
402
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
403
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "RUC" }),
404
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "1234567890001" })
405
+ ] })
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsx(
409
+ Controller,
410
+ {
411
+ control: form.control,
412
+ name: "company_address",
413
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
414
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Dirección" }),
415
+ /* @__PURE__ */ jsx(Textarea, { ...field })
416
+ ] })
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
420
+ /* @__PURE__ */ jsx(
421
+ Controller,
422
+ {
423
+ control: form.control,
424
+ name: "company_phone",
425
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
426
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Teléfono" }),
427
+ /* @__PURE__ */ jsx(Input, { ...field })
428
+ ] })
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsx(
432
+ Controller,
433
+ {
434
+ control: form.control,
435
+ name: "company_email",
436
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
437
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Email de la Empresa" }),
438
+ /* @__PURE__ */ jsx(Input, { ...field })
439
+ ] })
440
+ }
441
+ )
442
+ ] }),
443
+ /* @__PURE__ */ jsx(
444
+ Controller,
445
+ {
446
+ control: form.control,
447
+ name: "notes",
448
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
449
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Notas" }),
450
+ /* @__PURE__ */ jsx(Textarea, { ...field })
451
+ ] })
452
+ }
453
+ ),
454
+ /* @__PURE__ */ jsx(
455
+ Controller,
456
+ {
457
+ control: form.control,
458
+ name: "company_logo",
459
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
460
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }),
461
+ /* @__PURE__ */ jsx(Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
462
+ field.value && /* @__PURE__ */ jsx("img", { src: field.value, alt: "Logo", className: "mt-2 h-24 w-24 object-contain" })
463
+ ] })
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsx(
467
+ Controller,
468
+ {
469
+ control: form.control,
470
+ name: "is_default",
471
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 bg-ui-bg-subtle rounded-lg p-3", children: [
472
+ /* @__PURE__ */ jsx(
473
+ Switch,
474
+ {
475
+ checked: field.value,
476
+ onCheckedChange: field.onChange
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsxs("div", { children: [
480
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Empresa por defecto" }),
481
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Se usará en plantillas que no tengan empresa asignada." })
482
+ ] })
483
+ ] })
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-2", children: [
487
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "Cancelar" }),
488
+ /* @__PURE__ */ jsx(Button, { type: "submit", isLoading: saveMutation.isPending, children: "Guardar Cambios" })
489
+ ] })
490
+ ] }) })
491
+ ] });
492
+ };
493
+ const NewCompanyPage = () => {
494
+ const navigate = useNavigate();
495
+ const form = useForm({
496
+ defaultValues: {
497
+ company_name: "",
498
+ company_ruc: "",
499
+ company_address: "",
500
+ company_phone: "",
501
+ company_email: "",
502
+ company_logo: "",
503
+ notes: "",
504
+ admin_notification_email: "",
505
+ is_default: false
506
+ }
507
+ });
508
+ const createMutation = useMutation({
509
+ mutationFn: (payload) => sdk.client.fetch("/admin/invoice-config", {
510
+ method: "POST",
511
+ body: payload
512
+ }),
513
+ onSuccess: () => {
514
+ toast.success("Empresa creada exitosamente");
515
+ navigate("/invoice-config/companies");
516
+ },
517
+ onError: () => toast.error("Error al crear la empresa")
518
+ });
519
+ const uploadLogo = async (event) => {
520
+ var _a;
521
+ const file = (_a = event.target.files) == null ? void 0 : _a[0];
522
+ if (!file) return;
523
+ try {
524
+ const { files } = await sdk.admin.upload.create({ files: [file] });
525
+ form.setValue("company_logo", files[0].url);
526
+ } catch {
527
+ toast.error("Error al subir el logo.");
528
+ }
529
+ };
530
+ const handleSubmit = form.handleSubmit((data) => createMutation.mutate(data));
531
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
532
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
533
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Nueva Empresa" }),
534
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "← Volver" })
535
+ ] }),
536
+ /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 p-6 max-w-2xl", children: [
537
+ /* @__PURE__ */ jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-4 mb-2", children: /* @__PURE__ */ jsx(
538
+ Controller,
539
+ {
540
+ control: form.control,
541
+ name: "admin_notification_email",
542
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
543
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }),
544
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "admin@empresa.com" }),
545
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Notificaciones de nuevos pedidos a este email." })
546
+ ] })
547
+ }
548
+ ) }),
549
+ /* @__PURE__ */ jsx(
550
+ Controller,
551
+ {
552
+ control: form.control,
553
+ name: "company_name",
554
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
555
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Nombre de la Empresa *" }),
556
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "Mi Empresa S.A.S." })
557
+ ] })
558
+ }
559
+ ),
560
+ /* @__PURE__ */ jsx(
561
+ Controller,
562
+ {
563
+ control: form.control,
564
+ name: "company_ruc",
565
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
566
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "RUC" }),
567
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "1234567890001" })
568
+ ] })
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsx(
572
+ Controller,
573
+ {
574
+ control: form.control,
575
+ name: "company_address",
576
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
577
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Dirección" }),
578
+ /* @__PURE__ */ jsx(Textarea, { ...field, placeholder: "Av. Principal 123, Quito" })
579
+ ] })
580
+ }
581
+ ),
582
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
583
+ /* @__PURE__ */ jsx(
584
+ Controller,
585
+ {
586
+ control: form.control,
587
+ name: "company_phone",
588
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
589
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Teléfono" }),
590
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "+593 99 123 4567" })
591
+ ] })
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx(
595
+ Controller,
596
+ {
597
+ control: form.control,
598
+ name: "company_email",
599
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
600
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Email de la Empresa" }),
601
+ /* @__PURE__ */ jsx(Input, { ...field, placeholder: "info@empresa.com" })
602
+ ] })
603
+ }
604
+ )
605
+ ] }),
606
+ /* @__PURE__ */ jsx(
607
+ Controller,
608
+ {
609
+ control: form.control,
610
+ name: "notes",
611
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
612
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Notas" }),
613
+ /* @__PURE__ */ jsx(Textarea, { ...field, placeholder: "Notas internas o pie de página del documento" })
614
+ ] })
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsx(
618
+ Controller,
619
+ {
620
+ control: form.control,
621
+ name: "company_logo",
622
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
623
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }),
624
+ /* @__PURE__ */ jsx(Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
625
+ field.value && /* @__PURE__ */ jsx("img", { src: field.value, alt: "Logo", className: "mt-2 h-24 w-24 object-contain" })
626
+ ] })
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx(
630
+ Controller,
631
+ {
632
+ control: form.control,
633
+ name: "is_default",
634
+ render: ({ field }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 bg-ui-bg-subtle rounded-lg p-3", children: [
635
+ /* @__PURE__ */ jsx(
636
+ Switch,
637
+ {
638
+ checked: field.value,
639
+ onCheckedChange: field.onChange
640
+ }
641
+ ),
642
+ /* @__PURE__ */ jsxs("div", { children: [
643
+ /* @__PURE__ */ jsx(Label, { size: "small", weight: "plus", children: "Empresa por defecto" }),
644
+ /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Se usará en plantillas que no tengan empresa asignada." })
645
+ ] })
646
+ ] })
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-2", children: [
650
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "Cancelar" }),
651
+ /* @__PURE__ */ jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: "Crear Empresa" })
652
+ ] })
653
+ ] }) })
654
+ ] });
655
+ };
342
656
  const NewTemplatePage = () => {
343
657
  const navigate = useNavigate();
344
658
  const [name, setName] = useState("");
@@ -429,66 +743,139 @@ function getDefaultHtml(type) {
429
743
  </body>
430
744
  </html>`;
431
745
  }
432
- const VARIABLES = {
433
- order_invoice: [
434
- "company_name",
435
- "company_ruc",
436
- "company_address",
437
- "company_phone",
438
- "company_email",
439
- "company_logo_base64",
440
- "invoice_id",
441
- "invoice_date",
442
- "order_display_id",
443
- "order_date",
444
- "billing_address",
445
- "shipping_address",
446
- "cedula",
447
- "subtotal",
448
- "tax_total",
449
- "shipping_total",
450
- "discount_total",
451
- "total",
452
- "is_home_delivery",
453
- "notes",
454
- "{{#each items}}",
455
- "this.title",
456
- "this.variant_title",
457
- "this.quantity",
458
- "this.unit_price",
459
- "this.total",
460
- "{{/each}}"
461
- ],
462
- quote_proforma: [
463
- "company_name",
464
- "company_website",
465
- "quote_number",
466
- "date_str",
467
- "service_name",
468
- "is_home_delivery",
469
- "{{#each config_fields}}",
470
- "this.label",
471
- "this.value",
472
- "{{/each}}",
473
- "{{#each breakdown}}",
474
- "this.label",
475
- "this.total_formatted",
476
- "{{/each}}",
477
- "totals.subtotal_formatted",
478
- "totals.extras_formatted",
479
- "totals.discount_formatted",
480
- "totals.shipping_formatted",
481
- "totals.taxes_provided",
482
- "totals.taxes_formatted",
483
- "totals.total_formatted",
484
- "{{#each includes_left}}",
485
- "{{#each includes_right}}",
486
- "{{#each contact_rows}}",
487
- "this.label",
488
- "this.value",
489
- "{{/each}}"
490
- ]
746
+ const COMPANY_VARIABLES = [
747
+ { name: "company_name" },
748
+ { name: "company_ruc" },
749
+ { name: "company_address" },
750
+ { name: "company_phone" },
751
+ { name: "company_email" },
752
+ { name: "company_logo_base64" }
753
+ ];
754
+ const ORDER_INVOICE_CATEGORIES = [
755
+ {
756
+ label: "Empresa",
757
+ icon: "🏢",
758
+ variables: COMPANY_VARIABLES
759
+ },
760
+ {
761
+ label: "Documento",
762
+ icon: "📄",
763
+ variables: [
764
+ { name: "invoice_id" },
765
+ { name: "invoice_date" },
766
+ { name: "order_display_id" },
767
+ { name: "order_date" }
768
+ ]
769
+ },
770
+ {
771
+ label: "Cliente",
772
+ icon: "👤",
773
+ variables: [
774
+ { name: "billing_address" },
775
+ { name: "shipping_address" },
776
+ { name: "cedula" }
777
+ ]
778
+ },
779
+ {
780
+ label: "Productos",
781
+ icon: "📦",
782
+ variables: [
783
+ { name: "{{#each items}}", isBlock: true },
784
+ { name: "this.title" },
785
+ { name: "this.variant_title" },
786
+ { name: "this.quantity" },
787
+ { name: "this.unit_price" },
788
+ { name: "this.total" },
789
+ { name: "{{/each}}", isBlock: true }
790
+ ]
791
+ },
792
+ {
793
+ label: "Totales",
794
+ icon: "💰",
795
+ variables: [
796
+ { name: "subtotal" },
797
+ { name: "tax_total" },
798
+ { name: "shipping_total" },
799
+ { name: "discount_total" },
800
+ { name: "total" }
801
+ ]
802
+ },
803
+ {
804
+ label: "Otros",
805
+ icon: "📝",
806
+ variables: [
807
+ { name: "is_home_delivery" },
808
+ { name: "notes" }
809
+ ]
810
+ }
811
+ ];
812
+ const QUOTE_PROFORMA_CATEGORIES = [
813
+ {
814
+ label: "Empresa",
815
+ icon: "🏢",
816
+ variables: [...COMPANY_VARIABLES, { name: "company_website" }]
817
+ },
818
+ {
819
+ label: "Documento",
820
+ icon: "📄",
821
+ variables: [
822
+ { name: "quote_number" },
823
+ { name: "date_str" },
824
+ { name: "service_name" },
825
+ { name: "is_home_delivery" }
826
+ ]
827
+ },
828
+ {
829
+ label: "Configuración",
830
+ icon: "⚙️",
831
+ variables: [
832
+ { name: "{{#each config_fields}}", isBlock: true },
833
+ { name: "this.label" },
834
+ { name: "this.value" },
835
+ { name: "{{/each}}", isBlock: true }
836
+ ]
837
+ },
838
+ {
839
+ label: "Desglose",
840
+ icon: "📊",
841
+ variables: [
842
+ { name: "{{#each breakdown}}", isBlock: true },
843
+ { name: "this.label" },
844
+ { name: "this.total_formatted" },
845
+ { name: "{{/each}}", isBlock: true }
846
+ ]
847
+ },
848
+ {
849
+ label: "Totales",
850
+ icon: "💰",
851
+ variables: [
852
+ { name: "totals.subtotal_formatted" },
853
+ { name: "totals.extras_formatted" },
854
+ { name: "totals.discount_formatted" },
855
+ { name: "totals.shipping_formatted" },
856
+ { name: "totals.taxes_provided" },
857
+ { name: "totals.taxes_formatted" },
858
+ { name: "totals.total_formatted" }
859
+ ]
860
+ },
861
+ {
862
+ label: "Incluye y Contacto",
863
+ icon: "📋",
864
+ variables: [
865
+ { name: "{{#each includes_left}}", isBlock: true },
866
+ { name: "{{#each includes_right}}", isBlock: true },
867
+ { name: "{{#each contact_rows}}", isBlock: true },
868
+ { name: "this.label" },
869
+ { name: "this.value" },
870
+ { name: "{{/each}}", isBlock: true }
871
+ ]
872
+ }
873
+ ];
874
+ const CATEGORIES = {
875
+ order_invoice: ORDER_INVOICE_CATEGORIES,
876
+ quote_proforma: QUOTE_PROFORMA_CATEGORIES
491
877
  };
878
+ const PLACEHOLDER_LOGO_BASE64 = "data:image/svg+xml;base64," + btoa(`<svg xmlns="http://www.w3.org/2000/svg" width="120" height="40" viewBox="0 0 120 40"><rect width="120" height="40" rx="4" fill="#e2e8f0"/><text x="60" y="24" text-anchor="middle" fill="#64748b" font-family="sans-serif" font-size="11">LOGO</text></svg>`);
492
879
  const SAMPLE_DATA = {
493
880
  order_invoice: {
494
881
  company_name: "Mi Empresa",
@@ -496,7 +883,7 @@ const SAMPLE_DATA = {
496
883
  company_address: "Av. Principal 123, Quito",
497
884
  company_phone: "+593 99 123 4567",
498
885
  company_email: "info@miempresa.com",
499
- company_logo_base64: "",
886
+ company_logo_base64: PLACEHOLDER_LOGO_BASE64,
500
887
  invoice_id: "INV-000001",
501
888
  invoice_date: "19/06/2025",
502
889
  order_display_id: "000042",
@@ -518,6 +905,11 @@ const SAMPLE_DATA = {
518
905
  },
519
906
  quote_proforma: {
520
907
  company_name: "Mi Empresa",
908
+ company_ruc: "1234567890001",
909
+ company_address: "Av. Principal 123, Quito",
910
+ company_phone: "+593 99 123 4567",
911
+ company_email: "info@miempresa.com",
912
+ company_logo_base64: PLACEHOLDER_LOGO_BASE64,
521
913
  company_website: "www.miempresa.com",
522
914
  quote_number: "QP-2025-0001",
523
915
  date_str: "19/06/2025",
@@ -561,14 +953,22 @@ const TemplateEditorPage = () => {
561
953
  queryKey: ["invoice-template", id],
562
954
  enabled: !!id
563
955
  });
956
+ const { data: companiesData } = useQuery({
957
+ queryFn: () => sdk.client.fetch("/admin/invoice-config"),
958
+ queryKey: ["invoice-configs"]
959
+ });
564
960
  const template = data == null ? void 0 : data.invoice_template;
961
+ const companies = (companiesData == null ? void 0 : companiesData.invoice_configs) ?? [];
565
962
  const [name, setName] = useState("");
566
963
  const [htmlContent, setHtmlContent] = useState("");
964
+ const [companyId, setCompanyId] = useState(null);
567
965
  const [showVariables, setShowVariables] = useState(false);
966
+ const [collapsedSections, setCollapsedSections] = useState({});
568
967
  useEffect(() => {
569
968
  if (template) {
570
969
  setName(template.name);
571
970
  setHtmlContent(template.html_content);
971
+ setCompanyId(template.company_id);
572
972
  }
573
973
  }, [template]);
574
974
  const saveMutation = useMutation({
@@ -585,7 +985,7 @@ const TemplateEditorPage = () => {
585
985
  }
586
986
  });
587
987
  const handleSave = () => {
588
- saveMutation.mutate({ name, html_content: htmlContent });
988
+ saveMutation.mutate({ name, html_content: htmlContent, company_id: companyId });
589
989
  };
590
990
  const handlePreviewPdf = async () => {
591
991
  try {
@@ -606,7 +1006,7 @@ const TemplateEditorPage = () => {
606
1006
  if (!(template == null ? void 0 : template.is_default)) return;
607
1007
  if (!confirm("¿Restaurar la plantilla a su contenido original?")) return;
608
1008
  try {
609
- const res = await sdk.client.fetch(
1009
+ await sdk.client.fetch(
610
1010
  `/admin/invoice-templates/${id}/restore`,
611
1011
  { method: "POST" }
612
1012
  );
@@ -625,77 +1025,102 @@ const TemplateEditorPage = () => {
625
1025
  return `<div style="color:red;padding:20px;">Error en la plantilla Handlebars</div>`;
626
1026
  }
627
1027
  }, [htmlContent, template]);
628
- const insertVariable = useCallback((variable) => {
629
- const isBlock = variable.startsWith("{{#") || variable.startsWith("{{/");
1028
+ const insertVariable = useCallback((variable, isBlock) => {
630
1029
  const insertion = isBlock ? variable : `{{${variable}}}`;
631
1030
  setHtmlContent((prev) => prev + insertion);
632
1031
  }, []);
633
- const availableVars = template ? VARIABLES[template.type] ?? [] : [];
1032
+ const toggleSection = useCallback((label) => {
1033
+ setCollapsedSections((prev) => ({ ...prev, [label]: !prev[label] }));
1034
+ }, []);
1035
+ const categories = template ? CATEGORIES[template.type] ?? [] : [];
634
1036
  if (isLoading) {
635
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) });
1037
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
636
1038
  }
637
1039
  if (!template) {
638
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Plantilla no encontrada" }) });
1040
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: "Plantilla no encontrada" }) });
639
1041
  }
640
- return /* @__PURE__ */ jsxs(Container, { children: [
641
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
642
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1042
+ return /* @__PURE__ */ jsxs(Container, { className: "p-0", children: [
1043
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-ui-border-base", children: [
1044
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
643
1045
  /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => navigate("/invoice-config/invoice-templates"), children: "← Volver" }),
644
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
1046
+ /* @__PURE__ */ jsx(
645
1047
  Input,
646
1048
  {
647
1049
  value: name,
648
1050
  onChange: (e) => setName(e.target.value),
649
- style: { fontWeight: "bold", fontSize: 16 }
1051
+ className: "font-semibold text-base w-64"
650
1052
  }
651
- ) }),
652
- /* @__PURE__ */ jsxs("code", { style: { fontSize: 12, color: "#6c757d" }, children: [
1053
+ ),
1054
+ /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-muted text-xs", children: [
653
1055
  template.slug,
654
1056
  " (",
655
1057
  template.type,
656
1058
  ")"
657
1059
  ] })
658
1060
  ] }),
659
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
660
- template.is_default && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar Default" }),
1061
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1062
+ /* @__PURE__ */ jsxs(
1063
+ Select,
1064
+ {
1065
+ value: companyId ?? "__none__",
1066
+ onValueChange: (v) => setCompanyId(v === "__none__" ? null : v),
1067
+ children: [
1068
+ /* @__PURE__ */ jsx(Select.Trigger, { className: "w-48", children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Empresa" }) }),
1069
+ /* @__PURE__ */ jsxs(Select.Content, { children: [
1070
+ /* @__PURE__ */ jsx(Select.Item, { value: "__none__", children: "Sin empresa (usa default)" }),
1071
+ companies.map((c) => /* @__PURE__ */ jsxs(Select.Item, { value: c.id, children: [
1072
+ c.company_name,
1073
+ c.is_default ? " ★" : ""
1074
+ ] }, c.id))
1075
+ ] })
1076
+ ]
1077
+ }
1078
+ ),
1079
+ template.is_default && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar" }),
661
1080
  /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowVariables(!showVariables), children: showVariables ? "Ocultar Variables" : "Variables" }),
662
1081
  /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: handlePreviewPdf, children: "Preview PDF" }),
663
1082
  /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: saveMutation.isPending, children: "Guardar" })
664
1083
  ] })
665
1084
  ] }),
666
- showVariables && /* @__PURE__ */ jsxs("div", { style: {
667
- background: "#f8f9fa",
668
- border: "1px solid #e2e8f0",
669
- borderRadius: 8,
670
- padding: 12,
671
- marginBottom: 16,
672
- display: "flex",
673
- flexWrap: "wrap",
674
- gap: 6
675
- }, children: [
676
- /* @__PURE__ */ jsx(Label, { style: { width: "100%", marginBottom: 4 }, children: "Variables Handlebars — click para insertar:" }),
677
- availableVars.map((v, i) => /* @__PURE__ */ jsx(
678
- "button",
679
- {
680
- onClick: () => insertVariable(v),
681
- style: {
682
- background: v.startsWith("{{") ? "#e2e8f0" : "#dbeafe",
683
- border: "1px solid #cbd5e0",
684
- borderRadius: 4,
685
- padding: "2px 8px",
686
- fontSize: 11,
687
- cursor: "pointer",
688
- fontFamily: "monospace"
1085
+ showVariables && /* @__PURE__ */ jsxs("div", { className: "border-b border-ui-border-base bg-ui-bg-subtle px-4 py-3", children: [
1086
+ /* @__PURE__ */ jsx(Label, { className: "block mb-2 text-sm", children: "Variables Handlebars — click para insertar" }),
1087
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: categories.map((cat) => /* @__PURE__ */ jsxs("div", { className: "border border-ui-border-base rounded-lg bg-ui-bg-base overflow-hidden", children: [
1088
+ /* @__PURE__ */ jsxs(
1089
+ "button",
1090
+ {
1091
+ type: "button",
1092
+ className: "w-full flex items-center justify-between px-3 py-2 text-left text-sm font-medium hover:bg-ui-bg-subtle-hover transition-colors",
1093
+ onClick: () => toggleSection(cat.label),
1094
+ children: [
1095
+ /* @__PURE__ */ jsxs("span", { children: [
1096
+ cat.icon,
1097
+ " ",
1098
+ cat.label
1099
+ ] }),
1100
+ /* @__PURE__ */ jsxs("span", { className: "text-ui-fg-muted text-xs", children: [
1101
+ collapsedSections[cat.label] ? "" : "",
1102
+ " ",
1103
+ cat.variables.length
1104
+ ] })
1105
+ ]
1106
+ }
1107
+ ),
1108
+ !collapsedSections[cat.label] && /* @__PURE__ */ jsx("div", { className: "px-3 pb-2 flex flex-wrap gap-1.5", children: cat.variables.map((v, i) => /* @__PURE__ */ jsx(
1109
+ Badge,
1110
+ {
1111
+ color: v.isBlock ? "orange" : "blue",
1112
+ className: "cursor-pointer hover:opacity-80 transition-opacity text-xs",
1113
+ onClick: () => insertVariable(v.name, v.isBlock),
1114
+ children: v.isBlock ? v.name : `{{${v.name}}}`
689
1115
  },
690
- children: v.startsWith("{{") ? v : `{{${v}}}`
691
- },
692
- i
693
- ))
1116
+ `${cat.label}-${i}`
1117
+ )) })
1118
+ ] }, cat.label)) })
694
1119
  ] }),
695
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)" }, children: [
1120
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)", padding: 16 }, children: [
696
1121
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
697
- /* @__PURE__ */ jsx(Label, { style: { marginBottom: 8 }, children: "Editor HTML + Handlebars" }),
698
- /* @__PURE__ */ jsx("div", { style: { flex: 1, border: "1px solid #e2e8f0", borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx(
1122
+ /* @__PURE__ */ jsx(Label, { className: "mb-2", children: "Editor HTML + Handlebars" }),
1123
+ /* @__PURE__ */ jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-hidden", children: /* @__PURE__ */ jsx(
699
1124
  CodeMirror,
700
1125
  {
701
1126
  ref: editorRef,
@@ -709,14 +1134,8 @@ const TemplateEditorPage = () => {
709
1134
  ) })
710
1135
  ] }),
711
1136
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
712
- /* @__PURE__ */ jsx(Label, { style: { marginBottom: 8 }, children: "Preview en vivo (datos de ejemplo)" }),
713
- /* @__PURE__ */ jsx("div", { style: {
714
- flex: 1,
715
- border: "1px solid #e2e8f0",
716
- borderRadius: 8,
717
- overflow: "auto",
718
- background: "#fff"
719
- }, children: /* @__PURE__ */ jsx(
1137
+ /* @__PURE__ */ jsx(Label, { className: "mb-2", children: "Preview en vivo (datos de ejemplo)" }),
1138
+ /* @__PURE__ */ jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-auto bg-white", children: /* @__PURE__ */ jsx(
720
1139
  "iframe",
721
1140
  {
722
1141
  srcDoc: previewHtml,
@@ -742,10 +1161,22 @@ const routeModule = {
742
1161
  Component: InvoiceConfigPage,
743
1162
  path: "/invoice-config"
744
1163
  },
1164
+ {
1165
+ Component: CompaniesPage,
1166
+ path: "/invoice-config/companies"
1167
+ },
745
1168
  {
746
1169
  Component: InvoiceTemplatesPage,
747
1170
  path: "/invoice-config/invoice-templates"
748
1171
  },
1172
+ {
1173
+ Component: EditCompanyPage,
1174
+ path: "/invoice-config/companies/:id"
1175
+ },
1176
+ {
1177
+ Component: NewCompanyPage,
1178
+ path: "/invoice-config/companies/new"
1179
+ },
749
1180
  {
750
1181
  Component: NewTemplatePage,
751
1182
  path: "/invoice-config/invoice-templates/new"