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

@@ -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>