bloque 1: anticipo al reservar en linea y cobro automatico de membresias con enlace de pago (modulo skeen_cobros: enlaces con token firmado, webhooks de Stripe/MercadoPago, modo simulado para pruebas, pagina publica de pago, expiracion automatica y reembolso, reintentos de cobro, permisos y reporte); apagado por defecto

This commit is contained in:
2026-10-11 23:54:47 +00:00
parent 5fc0d2a780
commit 6a6c2a928b
35 changed files with 2841 additions and 32 deletions

View File

@@ -388,6 +388,8 @@ export const es: Dict = {
'reports.tab.cortes.desc': 'Arqueos y cierres por día',
'reports.tab.adeudos': 'Adeudos',
'reports.tab.adeudos.desc': 'Cartera por cobrar y planes de pago',
'reports.tab.cobros': 'Cobros',
'reports.tab.cobros.desc': 'Anticipos y membresías por cobrar (pendientes y expirados)',
'reports.tab.devoluciones': 'Devoluciones',
'reports.tab.devoluciones.desc': 'Ventas devueltas y montos',
'reports.tab.top-clientes': 'Top clientes',
@@ -487,6 +489,35 @@ export const es: Dict = {
'public.manage.leadTime': ' (con menos de {h} h de anticipación)',
'public.manage.contactPhone': ' Comunícate al {phone}.',
'public.manage.contactClinic': ' Comunícate con la clínica.',
// Anticipo al reservar en línea
'public.awaitingDeposit': 'Reserva retenida: falta tu anticipo',
'public.depositAmount': 'Anticipo a pagar: {amount}',
'public.depositHelp': 'Tienes {h} horas para pagar el anticipo; si no se paga, la reserva se libera.',
'public.payDeposit': 'Pagar anticipo',
'public.depositExpires': 'El enlace vence en {h} horas',
// --- Página pública de pago de un enlace de cobro ---
'pay.title': 'Pago en línea',
'pay.folioWord': 'Folio',
'pay.concept': 'Concepto',
'pay.amount': 'Monto',
'pay.forPatient': 'A nombre de {name}',
'pay.payNow': 'Pagar ahora',
'pay.paySimulated': 'Confirmar pago (modo prueba)',
'pay.paidOk': '¡Pago registrado!',
'pay.receipt': 'Comprobante',
'pay.date': 'Fecha',
'pay.notAvailable': 'El pago en línea no está disponible en este momento. Comunícate con la clínica.',
'pay.expiresAt': 'Este enlace vence el {date}',
'pay.expiredBody': 'Este enlace ya expiró. Pide uno nuevo a la clínica.',
'pay.cancelledBody': 'Este enlace fue cancelado.',
'pay.unavailableTitle': 'Enlace no disponible',
'pay.invalidLink': 'Enlace no válido',
'pay.notFound': 'No encontramos este enlace de pago.',
'pay.payError': 'No se pudo completar el pago. Intenta de nuevo.',
'pay.simulatedNote': 'Modo de prueba: no se cobra dinero real.',
'pay.secureNote': 'Pago protegido por la pasarela de la clínica.',
'pay.state.pagado': 'Pagado',
// --- POS ---
'pos.title': 'Punto de Venta',
@@ -603,8 +634,42 @@ export const es: Dict = {
'agenda.noDoctorsSubWeek': 'No hay médicos dados de alta para mostrar en la vista semanal.',
'agenda.noResourcesTitle': 'Sin recursos',
'agenda.noResourcesSub': 'Agrega recursos en Configuración › Recursos para ver esta vista.',
'agenda.depositPending': 'Esperando anticipo',
'config.idiomaTitle': 'Idioma de la interfaz',
'config.idiomaBody': 'Elige el idioma con el que se muestra el sistema en este navegador.',
// Anticipo de reservas en línea
'config.anticipo.title': 'Anticipo al reservar',
'config.anticipo.help': 'Pide un depósito al reservar en línea. La reserva queda retenida sin confirmar hasta que se pague; si no se paga a tiempo, se libera el horario.',
'config.anticipo.enable': 'Requerir anticipo',
'config.anticipo.enableHelp': 'Apagado, el portal reserva sin pedir dinero (como hasta hoy).',
'config.anticipo.type': 'Tipo de anticipo',
'config.anticipo.typePercent': 'Porcentaje del servicio',
'config.anticipo.typeFixed': 'Monto fijo',
'config.anticipo.value': 'Valor (porcentaje o monto)',
'config.anticipo.currency': 'Moneda',
'config.anticipo.hours': 'Horas para pagar',
'config.anticipo.refundable': 'Reembolsable',
'config.anticipo.refundableHelp': 'Si se cancela la cita, el anticipo se devuelve.',
'config.anticipo.simulated': 'Permitir pago simulado',
'config.anticipo.simulatedHelp': 'Permite probar el flujo sin pasarela ni dinero real. Déjalo apagado en producción.',
'config.anticipo.notice': 'Aviso para el cliente',
'config.anticipo.noticePlaceholder': 'Se requiere un anticipo para confirmar tu cita.',
// Cobro automático de membresías
'config.cobroAuto.title': 'Cobro automático de membresías',
'config.cobroAuto.help': 'Genera el enlace de pago del periodo y lo envía al paciente por notificaciones, con reintentos. El periodo queda pendiente hasta que se pague.',
'config.cobroAuto.enable': 'Activar cobro automático',
'config.cobroAuto.enableHelp': 'Apagado, el proceso solo genera el adeudo (no cobra ni avisa).',
'config.cobroAuto.method': 'Método',
'config.cobroAuto.methodLink': 'Enlace de pago',
'config.cobroAuto.methodCard': 'Tarjeta guardada (Stripe)',
'config.cobroAuto.daysBefore': 'Días antes del vencimiento',
'config.cobroAuto.grace': 'Días de gracia',
'config.cobroAuto.retries': 'Días de reintento del aviso',
'config.cobroAuto.retriesHelp': 'Días (respecto al vencimiento) en que se reintenta avisar, separados por coma.',
'config.cobroAuto.cancel': 'Vencer si no paga',
'config.cobroAuto.cancelHelp': 'Al agotar los reintentos, marca la membresía vencida. Apagado, sigue activa hasta el proceso normal de vencimiento.',
'config.cobroAuto.simulated': 'Permitir pago simulado',
'config.cobroAuto.simulatedHelp': 'Permite probar el flujo sin pasarela ni dinero real. Déjalo apagado en producción.',
};
// ------------------------------------------------------------
@@ -953,6 +1018,8 @@ export const en: Dict = {
'reports.tab.cortes.desc': 'Daily counts and closings',
'reports.tab.adeudos': 'Debts',
'reports.tab.adeudos.desc': 'Accounts receivable and payment plans',
'reports.tab.cobros': 'Charges',
'reports.tab.cobros.desc': 'Deposits and memberships pending collection (pending and expired)',
'reports.tab.devoluciones': 'Refunds',
'reports.tab.devoluciones.desc': 'Returned sales and amounts',
'reports.tab.top-clientes': 'Top clients',
@@ -1052,6 +1119,32 @@ export const en: Dict = {
'public.manage.leadTime': ' (less than {h} h in advance)',
'public.manage.contactPhone': ' Contact us at {phone}.',
'public.manage.contactClinic': ' Contact the clinic.',
'public.awaitingDeposit': 'Booking held: deposit pending',
'public.depositAmount': 'Deposit due: {amount}',
'public.depositHelp': 'You have {h} hours to pay the deposit; otherwise the booking is released.',
'public.payDeposit': 'Pay deposit',
'public.depositExpires': 'The link expires in {h} hours',
'pay.title': 'Online payment',
'pay.folioWord': 'Reference',
'pay.concept': 'Concept',
'pay.amount': 'Amount',
'pay.forPatient': 'For {name}',
'pay.payNow': 'Pay now',
'pay.paySimulated': 'Confirm payment (test mode)',
'pay.paidOk': 'Payment recorded!',
'pay.receipt': 'Receipt',
'pay.date': 'Date',
'pay.notAvailable': 'Online payment is not available right now. Please contact the clinic.',
'pay.expiresAt': 'This link expires on {date}',
'pay.expiredBody': 'This link has expired. Please ask the clinic for a new one.',
'pay.cancelledBody': 'This link was cancelled.',
'pay.unavailableTitle': 'Link unavailable',
'pay.invalidLink': 'Invalid link',
'pay.notFound': 'We could not find this payment link.',
'pay.payError': 'The payment could not be completed. Please try again.',
'pay.simulatedNote': 'Test mode: no real money is charged.',
'pay.secureNote': 'Payment protected by the clinic payment gateway.',
'pay.state.pagado': 'Paid',
// --- POS ---
'pos.title': 'Point of Sale',
@@ -1168,8 +1261,40 @@ export const en: Dict = {
'agenda.noDoctorsSubWeek': 'No doctors are registered to show in the weekly view.',
'agenda.noResourcesTitle': 'No resources',
'agenda.noResourcesSub': 'Add resources in Settings › Resources to see this view.',
'agenda.depositPending': 'Deposit pending',
'config.idiomaTitle': 'Interface language',
'config.idiomaBody': 'Choose the language the system displays in this browser.',
'config.anticipo.title': 'Deposit at booking',
'config.anticipo.help': 'Ask for a deposit when booking online. The booking is held without confirming until paid; if unpaid in time, the slot is released.',
'config.anticipo.enable': 'Require deposit',
'config.anticipo.enableHelp': 'Off, the portal books without asking for money (as today).',
'config.anticipo.type': 'Deposit type',
'config.anticipo.typePercent': 'Percentage of the service',
'config.anticipo.typeFixed': 'Fixed amount',
'config.anticipo.value': 'Value (percentage or amount)',
'config.anticipo.currency': 'Currency',
'config.anticipo.hours': 'Hours to pay',
'config.anticipo.refundable': 'Refundable',
'config.anticipo.refundableHelp': 'If the appointment is cancelled, the deposit is refunded.',
'config.anticipo.simulated': 'Allow simulated payment',
'config.anticipo.simulatedHelp': 'Lets you test the flow with no gateway and no real money. Keep it off in production.',
'config.anticipo.notice': 'Customer notice',
'config.anticipo.noticePlaceholder': 'A deposit is required to confirm your appointment.',
'config.cobroAuto.title': 'Automatic membership charging',
'config.cobroAuto.help': 'Generates the payment link for the period and sends it to the patient via notifications, with retries. The period stays pending until paid.',
'config.cobroAuto.enable': 'Enable automatic charging',
'config.cobroAuto.enableHelp': 'Off, the process only creates the charge (no charge or notice).',
'config.cobroAuto.method': 'Method',
'config.cobroAuto.methodLink': 'Payment link',
'config.cobroAuto.methodCard': 'Saved card (Stripe)',
'config.cobroAuto.daysBefore': 'Days before due date',
'config.cobroAuto.grace': 'Grace days',
'config.cobroAuto.retries': 'Notice retry days',
'config.cobroAuto.retriesHelp': 'Days (relative to due date) on which the notice is retried, comma separated.',
'config.cobroAuto.cancel': 'Expire if unpaid',
'config.cobroAuto.cancelHelp': 'When retries are exhausted, marks the membership as expired. Off, it stays active until the normal expiration process.',
'config.cobroAuto.simulated': 'Allow simulated payment',
'config.cobroAuto.simulatedHelp': 'Lets you test the flow with no gateway and no real money. Keep it off in production.',
};
export const DICT: Record<Locale, Dict> = { es, en };