feature/COVE-api-email-selector
This commit is contained in:
@@ -59,12 +59,16 @@ def trigger_cove_for_invoice(
|
||||
task=factura_cove_generate,
|
||||
tenant_id=tenant_id,
|
||||
company_id=body.company_id,
|
||||
# Hardcodeado a petición: siempre registrar esta tarea con el correo de Hugo Reyes.
|
||||
requested_by_user="hreyes@aduanasoft.com.mx",
|
||||
requested_by_user=(
|
||||
current_user.get("email")
|
||||
or current_user.get("preferred_username")
|
||||
or current_user.get("username")
|
||||
or "system"
|
||||
),
|
||||
task_name="factura_cove_generate",
|
||||
task_group="factura_cove",
|
||||
task_origin="a76/factura_cove/invoices/cove",
|
||||
args=[invoice_id, int(tenant_id), body.company_id],
|
||||
args=[invoice_id, int(tenant_id), body.company_id, body.recipient_email],
|
||||
)
|
||||
|
||||
return FacturaCoveResponse(
|
||||
|
||||
@@ -105,6 +105,7 @@ class GenerateCoveFromInvoiceRequest(BaseModel):
|
||||
|
||||
company_id: int
|
||||
force_regen: Optional[bool] = False
|
||||
recipient_email: Optional[EmailStr] = None
|
||||
|
||||
|
||||
class GenerateCoveResult(BaseModel):
|
||||
|
||||
@@ -107,6 +107,17 @@ class FacturaCoveDomainService:
|
||||
"""
|
||||
vu = ctx.vu
|
||||
|
||||
if not vu:
|
||||
errors.add_error(
|
||||
field="vu",
|
||||
message="La factura no tiene configuración VU asociada en el agente aduanal",
|
||||
solution=[
|
||||
"Configura los datos VU del agente aduanal y sube certificado (.cer) y llave (.key) antes de generar COVE."
|
||||
],
|
||||
code="MISSING_VU_CONFIGURATION",
|
||||
)
|
||||
return None
|
||||
|
||||
# Determinar usuario efectivo de WebService:
|
||||
# - Preferimos el usuario configurado en VU (web_service_user)
|
||||
# - Si no existe, usamos el de DODA-PITA (doda_web_service_user)
|
||||
@@ -567,7 +578,11 @@ class FacturaCoveDomainService:
|
||||
return mercancias
|
||||
|
||||
def build_factura_cove_request(
|
||||
self, invoice_id: int, tenant_id: int, company_id: int
|
||||
self,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
recipient_email: str | None = None,
|
||||
) -> FacturaCoveRequest:
|
||||
"""
|
||||
Construye el FacturaCoveRequest completo a partir de una factura,
|
||||
@@ -646,6 +661,8 @@ class FacturaCoveDomainService:
|
||||
# Fallback: recortar a máximo 10 caracteres para cumplir el esquema
|
||||
tipo_figura = raw_figura[:10]
|
||||
|
||||
correo_destino = (recipient_email or (ctx.vu.vu_email if ctx.vu else None) or "").strip() or None
|
||||
|
||||
return FacturaCoveRequest(
|
||||
configuracion_vu=configuracion_vu,
|
||||
# El RFC de consulta NO debe ser igual al RFC del que registra el comprobante.
|
||||
@@ -660,8 +677,7 @@ class FacturaCoveDomainService:
|
||||
patente_aduanal=patente_aduanal,
|
||||
fecha_expedicion=fecha_expedicion,
|
||||
observaciones=ctx.invoice.vu_observations or None,
|
||||
# Correo hardcodeado temporalmente para pruebas de COVE
|
||||
correo_electronico="hreyes@aduanasoft.com.mx",
|
||||
correo_electronico=correo_destino,
|
||||
tiene_subdivision=bool(ctx.invoice.logistics and ctx.invoice.logistics.is_subdivision),
|
||||
certificado_origen=False,
|
||||
numero_exportador_autorizado=None,
|
||||
@@ -675,7 +691,6 @@ class FacturaCoveDomainService:
|
||||
Versión "ligera" para frontend: evalúa si la factura puede generar COVE
|
||||
e informa por qué no, sin disparar la tarea Celery.
|
||||
"""
|
||||
db = self.db or CoreSessionLocal()
|
||||
errors = ErrorCollector()
|
||||
|
||||
try:
|
||||
|
||||
@@ -152,7 +152,13 @@ def _poll_external_status(
|
||||
|
||||
|
||||
@celery_app.task(bind=True, name="factura_cove_generate")
|
||||
def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_id: int) -> dict:
|
||||
def factura_cove_generate(
|
||||
self: Task,
|
||||
invoice_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
recipient_email: str | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Tarea Celery para preparar (y en el futuro generar) un COVE a partir de una factura.
|
||||
|
||||
@@ -173,7 +179,10 @@ def factura_cove_generate(self: Task, invoice_id: int, tenant_id: int, company_i
|
||||
|
||||
# Esta llamada valida todo y construye el payload; si algo falla, lanza ValidationException
|
||||
request_payload = service.build_factura_cove_request(
|
||||
invoice_id=invoice_id, tenant_id=tenant_id, company_id=company_id
|
||||
invoice_id=invoice_id,
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
recipient_email=recipient_email,
|
||||
)
|
||||
|
||||
_progress(self, 80, "Enviando solicitud al servicio COVE...")
|
||||
|
||||
29
backend/tests/unit/factura_cove/test_service.py
Normal file
29
backend/tests/unit/factura_cove/test_service.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
from api.v1.modules.a76.factura_cove.service import FacturaCoveDomainService, InvoiceContext
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
|
||||
def test_build_configuracion_vu_returns_validation_error_when_vu_is_missing() -> None:
|
||||
service = FacturaCoveDomainService(db=SimpleNamespace())
|
||||
ctx = InvoiceContext(
|
||||
invoice=SimpleNamespace(),
|
||||
broker=None,
|
||||
vu=None,
|
||||
)
|
||||
errors = ErrorCollector()
|
||||
|
||||
configuracion = service._build_configuracion_vu(ctx, errors)
|
||||
|
||||
assert configuracion is None
|
||||
assert errors.has_errors()
|
||||
assert errors.get_errors() == [
|
||||
{
|
||||
"field": "vu",
|
||||
"message": "La factura no tiene configuración VU asociada en el agente aduanal",
|
||||
"solution": [
|
||||
"Configura los datos VU del agente aduanal y sube certificado (.cer) y llave (.key) antes de generar COVE."
|
||||
],
|
||||
"code": "MISSING_VU_CONFIGURATION",
|
||||
}
|
||||
]
|
||||
@@ -545,15 +545,19 @@ export const invoicesApi = {
|
||||
}>(`/v1/a76/invoices/revert/${taskId}/status`);
|
||||
},
|
||||
|
||||
generateCove: (invoiceId: number, companyId: number) => {
|
||||
generateCove: (invoiceId: number, companyId: number, recipientEmail?: string | null) => {
|
||||
const params = new URLSearchParams({
|
||||
company_id: companyId.toString()
|
||||
});
|
||||
const body: Record<string, unknown> = {
|
||||
company_id: companyId
|
||||
};
|
||||
if (recipientEmail) {
|
||||
body.recipient_email = recipientEmail;
|
||||
}
|
||||
return api.post<{ task_id: string }>(
|
||||
`/v1/a76/factura-cove/invoices/${invoiceId}/cove?${params.toString()}`,
|
||||
{
|
||||
company_id: companyId
|
||||
}
|
||||
body
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
import { createColumns } from '$lib/components/dashboard/invoices/columns.js';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { currentUser } from '$lib/auth';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { getCompany } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
import { usersAPI } from '$lib/api/dashboard/users';
|
||||
import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
@@ -41,7 +46,8 @@
|
||||
BadgeCent,
|
||||
ArrowRightLeft,
|
||||
Database,
|
||||
ChevronUp
|
||||
ChevronUp,
|
||||
Mail
|
||||
} from 'lucide-svelte';
|
||||
import DetailsDialog from '$lib/components/dashboard/invoices/details-dialog.svelte';
|
||||
import DeleteDialog from '$lib/components/dashboard/invoices/delete-dialog.svelte';
|
||||
@@ -57,6 +63,13 @@
|
||||
// Los datos iniciales vienen del servidor
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
type CoveRecipientOption = {
|
||||
email: string;
|
||||
label: string;
|
||||
description: string;
|
||||
source: 'company' | 'user';
|
||||
};
|
||||
|
||||
// Estado para filtros
|
||||
// Nota: Los query parameters invoice_type y operation_type se pueden usar para filtrar
|
||||
// Ejemplo: /dashboard/invoices?invoice_type=TEM&operation_type=imp
|
||||
@@ -425,10 +438,189 @@
|
||||
// Estado para el diálogo de progreso
|
||||
let showProgressDialog = $state(false);
|
||||
let isWinsaiiConfirmOpen = $state(false);
|
||||
let isCoveDialogOpen = $state(false);
|
||||
let currentTaskId = $state<string | null>(null);
|
||||
let currentStatusFunction = $state<((taskId: string) => Promise<any>) | null>(null);
|
||||
let progressDialogTitle = $state('Generando documento');
|
||||
let progressDialogSteps = $state<{ label: string; percent: number }[] | null>(null);
|
||||
let coveRecipientsLoading = $state(false);
|
||||
let coveRecipientsError = $state<string | null>(null);
|
||||
let coveRecipientEmail = $state('');
|
||||
let coveRecipientOptions = $state<CoveRecipientOption[]>([]);
|
||||
let coveRecipientsRequestId = 0;
|
||||
|
||||
const currentUserEmail = $derived(($currentUser?.email || '').trim());
|
||||
const selectedCoveRecipient = $derived(
|
||||
coveRecipientOptions.find(
|
||||
(option) => option.email.trim().toLowerCase() === coveRecipientEmail.trim().toLowerCase()
|
||||
) ?? null
|
||||
);
|
||||
|
||||
function normalizeEmail(email?: string | null) {
|
||||
return (email || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function createRecipientLabel(prefix: string, email: string) {
|
||||
return `${prefix} - ${email}`;
|
||||
}
|
||||
|
||||
async function loadCoveRecipients() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
coveRecipientsError = null;
|
||||
coveRecipientOptions = [];
|
||||
coveRecipientEmail = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = ++coveRecipientsRequestId;
|
||||
coveRecipientsLoading = true;
|
||||
coveRecipientsError = null;
|
||||
|
||||
try {
|
||||
const [companyResult, usersResult] = await Promise.allSettled([
|
||||
getCompany(companyId),
|
||||
usersAPI.list(companyId, { page_size: 100 })
|
||||
]);
|
||||
|
||||
if (requestId !== coveRecipientsRequestId) return;
|
||||
|
||||
const recipientMap = new Map<string, CoveRecipientOption>();
|
||||
|
||||
const addRecipient = (
|
||||
email?: string | null,
|
||||
label?: string,
|
||||
description?: string,
|
||||
source: 'company' | 'user' = 'user'
|
||||
) => {
|
||||
const normalizedEmail = normalizeEmail(email);
|
||||
if (!normalizedEmail) return;
|
||||
if (recipientMap.has(normalizedEmail)) return;
|
||||
recipientMap.set(normalizedEmail, {
|
||||
email: email!.trim(),
|
||||
label: label || email!.trim(),
|
||||
description: description || email!.trim(),
|
||||
source
|
||||
});
|
||||
};
|
||||
|
||||
if (companyResult.status === 'fulfilled' && companyResult.value.data) {
|
||||
const company = companyResult.value.data as Record<string, any>;
|
||||
addRecipient(
|
||||
company.vu_email,
|
||||
'Correo VU de la empresa',
|
||||
company.name ? `Empresa ${company.name}` : 'Correo de ventanilla única',
|
||||
'company'
|
||||
);
|
||||
addRecipient(
|
||||
company.main_email,
|
||||
'Correo principal de la empresa',
|
||||
company.name ? `Empresa ${company.name}` : 'Correo principal',
|
||||
'company'
|
||||
);
|
||||
addRecipient(
|
||||
company.ind1_email,
|
||||
'Correo industrial 1',
|
||||
company.name ? `Empresa ${company.name}` : 'Correo industrial 1',
|
||||
'company'
|
||||
);
|
||||
addRecipient(
|
||||
company.ind2_email,
|
||||
'Correo industrial 2',
|
||||
company.name ? `Empresa ${company.name}` : 'Correo industrial 2',
|
||||
'company'
|
||||
);
|
||||
}
|
||||
|
||||
if (usersResult.status === 'fulfilled' && usersResult.value.users) {
|
||||
for (const user of usersResult.value.users) {
|
||||
const fullName = `${user.first_name || ''} ${user.last_name || ''}`.trim();
|
||||
addRecipient(
|
||||
user.email,
|
||||
fullName || user.username || user.email,
|
||||
createRecipientLabel('Usuario de la empresa', user.email),
|
||||
'user'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUserEmail) {
|
||||
addRecipient(
|
||||
currentUserEmail,
|
||||
'Mi correo',
|
||||
createRecipientLabel('Usuario autenticado', currentUserEmail),
|
||||
'user'
|
||||
);
|
||||
}
|
||||
|
||||
const recipients = [...recipientMap.values()].sort((left, right) => {
|
||||
if (left.source !== right.source) {
|
||||
return left.source === 'company' ? -1 : 1;
|
||||
}
|
||||
return left.label.localeCompare(right.label, 'es');
|
||||
});
|
||||
|
||||
const sourceFailures = [companyResult, usersResult].filter(
|
||||
(result) => result.status === 'rejected'
|
||||
).length;
|
||||
|
||||
coveRecipientOptions = recipients;
|
||||
|
||||
const preferredEmail = normalizeEmail(currentUserEmail);
|
||||
const existingSelection =
|
||||
recipients.find((option) => normalizeEmail(option.email) === normalizeEmail(coveRecipientEmail)) ||
|
||||
null;
|
||||
const preferredSelection =
|
||||
recipients.find((option) => normalizeEmail(option.email) === preferredEmail) || recipients[0] || null;
|
||||
|
||||
if (existingSelection) {
|
||||
coveRecipientEmail = existingSelection.email;
|
||||
} else if (preferredSelection) {
|
||||
coveRecipientEmail = preferredSelection.email;
|
||||
} else {
|
||||
coveRecipientEmail = '';
|
||||
}
|
||||
|
||||
if (recipientMap.size === 0 && sourceFailures > 0) {
|
||||
coveRecipientsError = 'No se pudieron cargar los correos disponibles para COVE';
|
||||
} else if (recipientMap.size === 0) {
|
||||
coveRecipientsError = 'No hay correos configurados para COVE';
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestId !== coveRecipientsRequestId) return;
|
||||
console.error('Error cargando correos de COVE:', error);
|
||||
coveRecipientsError = 'No se pudieron cargar los correos disponibles para COVE';
|
||||
} finally {
|
||||
if (requestId === coveRecipientsRequestId) {
|
||||
coveRecipientsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
const userEmail = currentUserEmail;
|
||||
|
||||
if (!companyId) {
|
||||
coveRecipientsError = null;
|
||||
coveRecipientOptions = [];
|
||||
coveRecipientEmail = '';
|
||||
return;
|
||||
}
|
||||
|
||||
void userEmail;
|
||||
void loadCoveRecipients();
|
||||
});
|
||||
|
||||
function openCoveDialog() {
|
||||
if (!selectedInvoice || !companyStore.activeCompany) {
|
||||
toast.info('Selecciona una factura para generar COVE');
|
||||
return;
|
||||
}
|
||||
|
||||
void loadCoveRecipients();
|
||||
isCoveDialogOpen = true;
|
||||
}
|
||||
|
||||
// Utilidad para convertir Base64 a Blob
|
||||
function base64ToBlob(base64: string, type: string) {
|
||||
@@ -808,12 +1000,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGenerateCove() {
|
||||
async function handleGenerateCove(recipientEmail = coveRecipientEmail) {
|
||||
if (!selectedInvoice || !companyStore.activeCompany) {
|
||||
toast.info('Selecciona una factura para generar COVE');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedRecipientEmail = normalizeEmail(recipientEmail);
|
||||
if (!selectedRecipientEmail) {
|
||||
toast.error('Selecciona un correo para enviar el COVE');
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany.id;
|
||||
|
||||
// Paso 1: Checar elegibilidad antes de disparar la tarea
|
||||
@@ -840,13 +1038,19 @@
|
||||
|
||||
// Paso 2: Disparar tarea Celery de COVE
|
||||
try {
|
||||
const response = await invoicesApi.generateCove(selectedInvoice.id, companyId);
|
||||
const response = await invoicesApi.generateCove(
|
||||
selectedInvoice.id,
|
||||
companyId,
|
||||
selectedRecipientEmail
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
toast.error(`Error al iniciar generación de COVE: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
isCoveDialogOpen = false;
|
||||
|
||||
currentTaskId = response.data!.task_id;
|
||||
currentStatusFunction = invoicesApi.getCoveStatus;
|
||||
progressDialogTitle = 'Validando datos para COVE';
|
||||
@@ -1065,6 +1269,101 @@
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
<Dialog.Root bind:open={isCoveDialogOpen}>
|
||||
<Dialog.Content class="sm:!max-w-2xl">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Generar COVE</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Selecciona el correo destinatario para la factura
|
||||
<strong>{selectedInvoice?.invoice_number}</strong>.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<Card.Root class="border bg-card/90 shadow-none">
|
||||
<Card.Content class="space-y-4 p-4 sm:p-5">
|
||||
<div class="space-y-2">
|
||||
<Label for="cove-recipient">Correo destinatario</Label>
|
||||
<Select.Root bind:value={coveRecipientEmail}>
|
||||
<Select.Trigger
|
||||
id="cove-recipient"
|
||||
class="h-auto min-h-14 w-full bg-background px-3 py-2"
|
||||
disabled={coveRecipientsLoading || coveRecipientOptions.length === 0}
|
||||
>
|
||||
{#snippet children()}
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3 text-left">
|
||||
<div class="flex size-9 shrink-0 items-center justify-center rounded-full bg-muted">
|
||||
<Mail class="size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">
|
||||
Destino COVE
|
||||
</div>
|
||||
<div class="truncate text-sm font-medium">
|
||||
{selectedCoveRecipient?.label || 'Selecciona un correo'}
|
||||
</div>
|
||||
<div class="truncate text-xs text-muted-foreground">
|
||||
{selectedCoveRecipient?.email || 'Se enviará al correo del usuario que generó la factura'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Select.Trigger>
|
||||
<Select.Content searchable searchPlaceholder="Buscar correo...">
|
||||
{#if coveRecipientsLoading}
|
||||
<div class="px-2 py-3 text-sm text-muted-foreground">
|
||||
Cargando correos disponibles...
|
||||
</div>
|
||||
{:else if coveRecipientOptions.length === 0}
|
||||
<div class="px-2 py-3 text-sm text-muted-foreground">
|
||||
No hay correos disponibles para COVE.
|
||||
</div>
|
||||
{:else}
|
||||
{#each coveRecipientOptions as recipient}
|
||||
<Select.Item
|
||||
value={recipient.email}
|
||||
label={recipient.label}
|
||||
searchText={`${recipient.label} ${recipient.email} ${recipient.description}`}
|
||||
>
|
||||
{#snippet children({ selected })}
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<span class="truncate font-medium">
|
||||
{recipient.label}
|
||||
{#if selected}
|
||||
<span class="ml-2 text-xs text-primary">Seleccionado</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="truncate text-xs text-muted-foreground">
|
||||
{recipient.description}
|
||||
</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{#if coveRecipientsError}
|
||||
<p class="text-sm text-destructive">{coveRecipientsError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={() => (isCoveDialogOpen = false)}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
onclick={() => handleGenerateCove(coveRecipientEmail)}
|
||||
disabled={coveRecipientsLoading || !coveRecipientEmail}
|
||||
>
|
||||
<Mail class="mr-2 h-4 w-4" />
|
||||
Generar COVE
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<PdfProgressDialog
|
||||
bind:open={showProgressDialog}
|
||||
taskId={currentTaskId}
|
||||
@@ -1132,9 +1431,12 @@
|
||||
<!-- Capa para cerrar el submenú al hacer click fuera -->
|
||||
<div
|
||||
class="fixed inset-0 z-40"
|
||||
on:click={() => (showVuSubmenu = false)}
|
||||
on:contextmenu|preventDefault={() => (showVuSubmenu = false)}
|
||||
/>
|
||||
onclick={() => (showVuSubmenu = false)}
|
||||
oncontextmenu={(event) => {
|
||||
event.preventDefault();
|
||||
showVuSubmenu = false;
|
||||
}}
|
||||
></div>
|
||||
<!-- Submenú contextual de Interface VU -->
|
||||
<div
|
||||
class="fixed z-50 min-w-48 rounded-md border bg-popover p-1 text-sm shadow-md"
|
||||
@@ -1143,7 +1445,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left hover:bg-accent"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showVuSubmenu = false;
|
||||
toast.info('Consulta VU - Próximamente');
|
||||
}}
|
||||
@@ -1154,7 +1456,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left hover:bg-accent"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showVuSubmenu = false;
|
||||
toast.info('Adenda VU - Próximamente');
|
||||
}}
|
||||
@@ -1165,9 +1467,9 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left hover:bg-accent"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showVuSubmenu = false;
|
||||
handleGenerateCove();
|
||||
openCoveDialog();
|
||||
}}
|
||||
>
|
||||
<Files class="mr-2 h-4 w-4" />
|
||||
@@ -1176,7 +1478,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left hover:bg-accent"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
showVuSubmenu = false;
|
||||
toast.info('COVE Masivos - Próximamente');
|
||||
}}
|
||||
@@ -1291,7 +1593,7 @@
|
||||
<!-- Interface VU: click izquierdo = COVE, click derecho = submenú -->
|
||||
<DropdownMenu.Item
|
||||
class="cursor-pointer"
|
||||
onclick={() => handleGenerateCove()}
|
||||
onclick={() => openCoveDialog()}
|
||||
oncontextmenu={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user