Improve teh securty of teh DB

This commit is contained in:
Urtzi Alfaro
2025-10-19 19:22:37 +02:00
parent 62971c07d7
commit 05da20357d
87 changed files with 7998 additions and 932 deletions

View File

@@ -97,15 +97,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
}
};
const handleDemoLogin = () => {
setCredentials({
email: 'admin@bakery.com',
password: 'admin12345',
remember_me: false
});
setErrors({});
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !isLoading) {
handleSubmit(e as any);
@@ -290,30 +281,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
<div id="login-button-description" className="sr-only">
Presiona Enter o haz clic para iniciar sesión con tus credenciales
</div>
{/* Demo Login Section */}
<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-border-primary" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-background-primary text-text-tertiary">Demo</span>
</div>
</div>
<div className="mt-6">
<Button
type="button"
variant="outline"
onClick={handleDemoLogin}
disabled={isLoading}
className="w-full focus:outline-none focus:ring-2 focus:ring-color-primary focus:ring-offset-2"
>
Usar credenciales de demostración
</Button>
</div>
</div>
</form>
{onRegisterClick && (

View File

@@ -1,10 +1,12 @@
import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../ui/Button';
import { Input } from '../../../ui/Input';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useCreateIngredient, useClassifyBatch } from '../../../../api/hooks/inventory';
import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks/sales';
import type { ImportValidationResult } from '../../../../api/types/sales';
import type { ImportValidationResponse } from '../../../../api/types/dataImport';
import type { ProductSuggestionResponse } from '../../../../api/types/inventory';
import { useAuth } from '../../../../contexts/AuthContext';
interface UploadSalesDataStepProps {
@@ -52,6 +54,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
onComplete,
isFirstStep
}) => {
const { t } = useTranslation();
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isValidating, setIsValidating] = useState(false);
const [validationResult, setValidationResult] = useState<ImportValidationResponse | null>(null);
@@ -60,6 +63,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
const [isCreating, setIsCreating] = useState(false);
const [error, setError] = useState<string>('');
const [progressState, setProgressState] = useState<ProgressState | null>(null);
const [showGuide, setShowGuide] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const currentTenant = useCurrentTenant();
@@ -132,7 +136,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
};
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResult) => {
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResponse) => {
if (!currentTenant?.id) {
setError('No hay datos de validación disponibles para generar sugerencias');
setIsValidating(false);
@@ -166,7 +170,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
const items: InventoryItem[] = classificationResponse.suggestions.map(suggestion => {
const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse) => {
// Calculate default stock quantity based on sales data
const defaultStock = Math.max(
Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply
@@ -534,6 +538,113 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
</p>
</div>
{/* File Format Guide */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-start justify-between">
<div className="flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 className="font-semibold text-blue-900">
{t('onboarding:steps.inventory_setup.file_format_guide.title', 'Guía de Formato de Archivo')}
</h3>
</div>
<button
onClick={() => setShowGuide(!showGuide)}
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
>
{showGuide
? t('onboarding:steps.inventory_setup.file_format_guide.collapse_guide', 'Ocultar Guía')
: t('onboarding:steps.inventory_setup.file_format_guide.toggle_guide', 'Ver Guía Completa')
}
</button>
</div>
{/* Quick Summary - Always Visible */}
<div className="text-sm text-blue-800 space-y-1">
<p>
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.title', 'Formatos Soportados')}:</strong>{' '}
CSV, JSON, Excel (XLSX) {t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.max_size', 'Tamaño máximo: 10MB')}
</p>
<p>
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}:</strong>{' '}
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')},{' '}
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')},{' '}
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}
</p>
</div>
{/* Detailed Guide - Collapsible */}
{showGuide && (
<div className="mt-4 pt-4 border-t border-blue-200 space-y-4">
{/* Required Columns Detail */}
<div>
<h4 className="font-semibold text-blue-900 mb-2">
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}
</h4>
<div className="text-sm text-blue-800 space-y-1 pl-4">
<p>
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')}:</strong>{' '}
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date_examples', 'date, fecha, data')}
</span>
</p>
<p>
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')}:</strong>{' '}
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product_examples', 'product, producto, product_name')}
</span>
</p>
<p>
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}:</strong>{' '}
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity_examples', 'quantity, cantidad, quantity_sold')}
</span>
</p>
</div>
</div>
{/* Optional Columns */}
<div>
<h4 className="font-semibold text-blue-900 mb-2">
{t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.title', 'Columnas Opcionales')}
</h4>
<div className="text-sm text-blue-800 space-y-1 pl-4">
<p> {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.revenue', 'Ingresos (revenue, ingresos, ventas)')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.unit_price', 'Precio Unitario (unit_price, precio, price)')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.category', 'Categoría (category, categoria)')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.sku', 'SKU del Producto')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.location', 'Ubicación/Tienda')}</p>
</div>
</div>
{/* Date Formats */}
<div>
<h4 className="font-semibold text-blue-900 mb-2">
{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.title', 'Formatos de Fecha Soportados')}
</h4>
<div className="text-sm text-blue-800 pl-4">
<p>{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.formats', 'YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, y más')}</p>
<p className="text-xs mt-1">{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.with_time', 'También se admiten formatos con hora')}</p>
</div>
</div>
{/* Automatic Features */}
<div>
<h4 className="font-semibold text-blue-900 mb-2">
{t('onboarding:steps.inventory_setup.file_format_guide.features.title', 'Características Automáticas')}
</h4>
<div className="text-sm text-blue-800 space-y-1 pl-4">
<p> {t('onboarding:steps.inventory_setup.file_format_guide.features.multilingual', 'Detección multiidioma de columnas')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.features.validation', 'Validación automática con reporte detallado')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.features.ai_classification', 'Clasificación de productos con IA')}</p>
<p> {t('onboarding:steps.inventory_setup.file_format_guide.features.inventory_suggestions', 'Sugerencias inteligentes de inventario')}</p>
</div>
</div>
</div>
)}
</div>
{/* File Upload Area */}
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
@@ -626,7 +737,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<div className="mt-2">
<p className="font-medium text-[var(--color-warning)]">Warnings:</p>
<ul className="list-disc list-inside">
{validationResult.warnings.map((warning, index) => (
{validationResult.warnings.map((warning: any, index: number) => (
<li key={index} className="text-[var(--color-warning)]">
{typeof warning === 'string' ? warning : JSON.stringify(warning)}
</li>

View File

@@ -1,19 +1,22 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { ArrowRight } from 'lucide-react';
import { SubscriptionPricingCards } from './SubscriptionPricingCards';
export const PricingSection: React.FC = () => {
const { t } = useTranslation();
return (
<section id="pricing" className="py-24 bg-[var(--bg-primary)]">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div className="text-center mb-8">
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
Planes que se Adaptan a tu Negocio
{t('landing:pricing.title', 'Planes que se Adaptan a tu Negocio')}
</h2>
<p className="mt-4 max-w-2xl mx-auto text-lg text-[var(--text-secondary)]">
Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.
{t('landing:pricing.subtitle', 'Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.')}
</p>
</div>
@@ -26,7 +29,7 @@ export const PricingSection: React.FC = () => {
to="/plans/compare"
className="text-[var(--color-primary)] hover:text-[var(--color-primary-dark)] font-semibold inline-flex items-center gap-2"
>
Ver comparación completa de características
{t('landing:pricing.compare_link', 'Ver comparación completa de características')}
<ArrowRight className="w-4 h-4" />
</Link>
</div>