Compare commits
2 Commits
e096ed1590
...
9a2b5a5f91
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a2b5a5f91 | |||
| ed243ccb78 |
@@ -34,8 +34,8 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
HttpOnly = true, // Seguridad: JS no puede leer esto
|
||||
Expires = DateTime.UtcNow.AddMinutes(15),
|
||||
Secure = false, // Solo HTTPS (Para tests locales 'Secure = false' temporalmente)
|
||||
SameSite = SameSiteMode.Lax, // Protección CSRF (Strict para máxima seguridad, pero puede ser Lax si hay problemas con redirecciones y testeos locales)
|
||||
Secure = true, // Solo HTTPS (Para tests locales 'Secure = false' temporalmente)
|
||||
SameSite = SameSiteMode.Strict, // Protección CSRF (Strict para máxima seguridad, pero puede ser Lax si hay problemas con redirecciones y testeos locales)
|
||||
IsEssential = true
|
||||
};
|
||||
Response.Cookies.Append(cookieName, token, cookieOptions);
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MotoresArgentinosV2.Core.Entities;
|
||||
using MotoresArgentinosV2.Core.Interfaces;
|
||||
|
||||
namespace MotoresArgentinosV2.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class OperacionesLegacyController : ControllerBase
|
||||
{
|
||||
private readonly IOperacionesLegacyService _operacionesService;
|
||||
private readonly ILogger<OperacionesLegacyController> _logger;
|
||||
|
||||
public OperacionesLegacyController(IOperacionesLegacyService operacionesService, ILogger<OperacionesLegacyController> logger)
|
||||
{
|
||||
_operacionesService = operacionesService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene los medios de pago disponibles
|
||||
/// </summary>
|
||||
[HttpGet("medios-pago")]
|
||||
public async Task<ActionResult<List<MedioDePago>>> GetMediosDePago()
|
||||
{
|
||||
try
|
||||
{
|
||||
var medios = await _operacionesService.ObtenerMediosDePagoAsync();
|
||||
return Ok(medios);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener medios de pago");
|
||||
return StatusCode(500, "Ocurrió un error interno al obtener medios de pago");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Busca una operación por su número de operación
|
||||
/// </summary>
|
||||
[HttpGet("{noperacion}")]
|
||||
public async Task<ActionResult<List<Operacion>>> GetOperacion(string noperacion)
|
||||
{
|
||||
try
|
||||
{
|
||||
var operaciones = await _operacionesService.ObtenerOperacionesPorNumeroAsync(noperacion);
|
||||
|
||||
if (operaciones == null || !operaciones.Any())
|
||||
{
|
||||
return NotFound($"No se encontraron operaciones con el número {noperacion}");
|
||||
}
|
||||
|
||||
return Ok(operaciones);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener operación {Noperacion}", noperacion);
|
||||
return StatusCode(500, "Ocurrió un error interno al buscar la operación");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene operaciones realizadas en un rango de fechas
|
||||
/// </summary>
|
||||
[HttpGet("buscar")]
|
||||
public async Task<ActionResult<List<Operacion>>> GetOperacionesPorFecha([FromQuery] DateTime fechaInicio, [FromQuery] DateTime fechaFin)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (fechaInicio > fechaFin)
|
||||
{
|
||||
return BadRequest("La fecha de inicio no puede ser mayor a la fecha de fin.");
|
||||
}
|
||||
|
||||
var operaciones = await _operacionesService.ObtenerOperacionesPorFechasAsync(fechaInicio, fechaFin);
|
||||
return Ok(operaciones);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al buscar operaciones por fecha");
|
||||
return StatusCode(500, "Ocurrió un error interno al buscar operaciones.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registra una nueva operación de pago
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> CrearOperacion([FromBody] Operacion operacion)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
// Validar campos mínimos necesarios si es necesario
|
||||
if (string.IsNullOrEmpty(operacion.Noperacion))
|
||||
{
|
||||
return BadRequest("El número de operación es obligatorio.");
|
||||
}
|
||||
|
||||
var resultado = await _operacionesService.InsertarOperacionAsync(operacion);
|
||||
|
||||
if (resultado)
|
||||
{
|
||||
return CreatedAtAction(nameof(GetOperacion), new { noperacion = operacion.Noperacion }, operacion);
|
||||
}
|
||||
|
||||
return BadRequest("No se pudo registrar la operación.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al crear operación {Noperacion}", operacion.Noperacion);
|
||||
return StatusCode(500, "Ocurrió un error interno al registrar la operación.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,16 +98,12 @@ builder.Services.Configure<HostOptions>(options =>
|
||||
builder.Services.AddDbContext<InternetDbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("Internet")));
|
||||
|
||||
builder.Services.AddDbContext<EldiaDbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("eldia")));
|
||||
|
||||
builder.Services.AddDbContext<MotoresV2DbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("MotoresV2"),
|
||||
sqlOptions => sqlOptions.EnableRetryOnFailure()));
|
||||
|
||||
// SERVICIOS
|
||||
builder.Services.AddScoped<IAvisosLegacyService, AvisosLegacyService>();
|
||||
builder.Services.AddScoped<IOperacionesLegacyService, OperacionesLegacyService>();
|
||||
builder.Services.AddScoped<IUsuariosLegacyService, UsuariosLegacyService>();
|
||||
builder.Services.AddScoped<IPasswordService, PasswordService>();
|
||||
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
using MotoresArgentinosV2.Core.Entities;
|
||||
|
||||
namespace MotoresArgentinosV2.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaz para servicios que interactúan con stored procedures legacy
|
||||
/// relacionados con operaciones de pago
|
||||
/// </summary>
|
||||
public interface IOperacionesLegacyService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ejecuta el SP sp_inserta_operaciones para registrar una nueva operación
|
||||
/// </summary>
|
||||
/// <param name="operacion">Datos de la operación a registrar</param>
|
||||
/// <returns>True si se insertó correctamente</returns>
|
||||
Task<bool> InsertarOperacionAsync(Operacion operacion);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene operaciones por número de operación
|
||||
/// </summary>
|
||||
/// <param name="noperacion">Número de operación a buscar</param>
|
||||
/// <returns>Lista de operaciones encontradas</returns>
|
||||
Task<List<Operacion>> ObtenerOperacionesPorNumeroAsync(string noperacion);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene operaciones en un rango de fechas
|
||||
/// </summary>
|
||||
/// <param name="fechaInicio">Fecha inicial</param>
|
||||
/// <param name="fechaFin">Fecha final</param>
|
||||
/// <returns>Lista de operaciones en el rango</returns>
|
||||
Task<List<Operacion>> ObtenerOperacionesPorFechasAsync(DateTime fechaInicio, DateTime fechaFin);
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene todos los medios de pago disponibles
|
||||
/// </summary>
|
||||
/// <returns>Lista de medios de pago</returns>
|
||||
Task<List<MedioDePago>> ObtenerMediosDePagoAsync();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MotoresArgentinosV2.Core.DTOs;
|
||||
using MotoresArgentinosV2.Core.Entities;
|
||||
|
||||
namespace MotoresArgentinosV2.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Contexto de Entity Framework unificado para la base de datos 'eldia' (Legacy)
|
||||
/// Contiene las tablas de avisos web, operaciones, medios de pago y lógica de usuarios legacy.
|
||||
/// </summary>
|
||||
public class EldiaDbContext : DbContext
|
||||
{
|
||||
public EldiaDbContext(DbContextOptions<EldiaDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
// Tablas de la base 'autos' (ahora en eldia)
|
||||
public DbSet<Operacion> Operaciones { get; set; }
|
||||
public DbSet<MedioDePago> MediosDePago { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// --- Configuración de tablas ex-Autos ---
|
||||
|
||||
modelBuilder.Entity<Operacion>(entity =>
|
||||
{
|
||||
entity.ToTable("operaciones");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Fecha).HasColumnName("fecha");
|
||||
entity.Property(e => e.Motivo).HasColumnName("motivo").HasMaxLength(50);
|
||||
entity.Property(e => e.Moneda).HasColumnName("moneda").HasMaxLength(50);
|
||||
entity.Property(e => e.Direccionentrega).HasColumnName("direccionentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Validaciondomicilio).HasColumnName("validaciondomicilio").HasMaxLength(50);
|
||||
entity.Property(e => e.Codigopedido).HasColumnName("codigopedido").HasMaxLength(50);
|
||||
entity.Property(e => e.Nombreentrega).HasColumnName("nombreentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Fechahora).HasColumnName("fechahora").HasMaxLength(50);
|
||||
entity.Property(e => e.Telefonocomprador).HasColumnName("telefonocomprador").HasMaxLength(50);
|
||||
entity.Property(e => e.Barrioentrega).HasColumnName("barrioentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Codautorizacion).HasColumnName("codautorizacion").HasMaxLength(50);
|
||||
entity.Property(e => e.Paisentrega).HasColumnName("paisentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Cuotas).HasColumnName("cuotas").HasMaxLength(50);
|
||||
entity.Property(e => e.Validafechanac).HasColumnName("validafechanac").HasMaxLength(50);
|
||||
entity.Property(e => e.Validanrodoc).HasColumnName("validanrodoc").HasMaxLength(50);
|
||||
entity.Property(e => e.Titular).HasColumnName("titular").HasMaxLength(50);
|
||||
entity.Property(e => e.Pedido).HasColumnName("pedido").HasMaxLength(50);
|
||||
entity.Property(e => e.Zipentrega).HasColumnName("zipentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Monto).HasColumnName("monto").HasMaxLength(50);
|
||||
entity.Property(e => e.Tarjeta).HasColumnName("tarjeta").HasMaxLength(50);
|
||||
entity.Property(e => e.Fechaentrega).HasColumnName("fechaentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Emailcomprador).HasColumnName("emailcomprador").HasMaxLength(50);
|
||||
entity.Property(e => e.Validanropuerta).HasColumnName("validanropuerta").HasMaxLength(50);
|
||||
entity.Property(e => e.Ciudadentrega).HasColumnName("ciudadentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Validatipodoc).HasColumnName("validatipodoc").HasMaxLength(50);
|
||||
entity.Property(e => e.Noperacion).HasColumnName("noperacion").HasMaxLength(50);
|
||||
entity.Property(e => e.Estadoentrega).HasColumnName("estadoentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Resultado).HasColumnName("resultado").HasMaxLength(50);
|
||||
entity.Property(e => e.Mensajeentrega).HasColumnName("mensajeentrega").HasMaxLength(50);
|
||||
entity.Property(e => e.Precioneto).HasColumnName("precioneto");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<MedioDePago>(entity =>
|
||||
{
|
||||
entity.ToTable("mediodepago");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Mediodepago).HasColumnName("mediodepago").HasMaxLength(20);
|
||||
});
|
||||
|
||||
// --- Configuración de DTOs Keyless de ex-Internet ---
|
||||
|
||||
modelBuilder.Entity<DatosAvisoDto>(e =>
|
||||
{
|
||||
e.HasNoKey();
|
||||
e.ToView(null);
|
||||
|
||||
e.Property(p => p.ImporteSiniva).HasColumnType("decimal(18,2)");
|
||||
e.Property(p => p.ImporteTotsiniva).HasColumnType("decimal(18,2)");
|
||||
e.Property(p => p.PorcentajeCombinado).HasColumnType("decimal(18,2)");
|
||||
e.Property(p => p.Centimetros).HasColumnType("decimal(18,2)");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MotoresArgentinosV2.Core.Entities;
|
||||
using MotoresArgentinosV2.Core.Interfaces;
|
||||
using MotoresArgentinosV2.Infrastructure.Data;
|
||||
|
||||
namespace MotoresArgentinosV2.Infrastructure.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implementación del servicio para interactuar con datos legacy de operaciones
|
||||
/// Utiliza EldiaDbContext para acceder a tablas y ejecutar SPs de la DB 'eldia' (ex base de datos 'autos')
|
||||
/// </summary>
|
||||
public class OperacionesLegacyService : IOperacionesLegacyService
|
||||
{
|
||||
private readonly EldiaDbContext _context;
|
||||
private readonly ILogger<OperacionesLegacyService> _logger;
|
||||
|
||||
public OperacionesLegacyService(EldiaDbContext context, ILogger<OperacionesLegacyService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejecuta el SP sp_inserta_operaciones para registrar un pago
|
||||
/// </summary>
|
||||
public async Task<bool> InsertarOperacionAsync(Operacion operacion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Ejecutando sp_inserta_operaciones para operación: {Noperacion}", operacion.Noperacion);
|
||||
|
||||
// Preparar parámetros asegurando manejo de nulos
|
||||
var parameters = new[]
|
||||
{
|
||||
new SqlParameter("@fecha", operacion.Fecha ?? (object)DBNull.Value),
|
||||
new SqlParameter("@Motivo", operacion.Motivo ?? (object)DBNull.Value),
|
||||
new SqlParameter("@Moneda", operacion.Moneda ?? (object)DBNull.Value),
|
||||
new SqlParameter("@Direccionentrega", operacion.Direccionentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@Validaciondomicilio", operacion.Validaciondomicilio ?? (object)DBNull.Value),
|
||||
new SqlParameter("@codigopedido", operacion.Codigopedido ?? (object)DBNull.Value),
|
||||
new SqlParameter("@nombreentrega", operacion.Nombreentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@fechahora", operacion.Fechahora ?? (object)DBNull.Value),
|
||||
new SqlParameter("@telefonocomprador", operacion.Telefonocomprador ?? (object)DBNull.Value),
|
||||
new SqlParameter("@barrioentrega", operacion.Barrioentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@codautorizacion", operacion.Codautorizacion ?? (object)DBNull.Value),
|
||||
new SqlParameter("@paisentrega", operacion.Paisentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@cuotas", operacion.Cuotas ?? (object)DBNull.Value),
|
||||
new SqlParameter("@validafechanac", operacion.Validafechanac ?? (object)DBNull.Value),
|
||||
new SqlParameter("@validanrodoc", operacion.Validanrodoc ?? (object)DBNull.Value),
|
||||
new SqlParameter("@titular", operacion.Titular ?? (object)DBNull.Value),
|
||||
new SqlParameter("@pedido", operacion.Pedido ?? (object)DBNull.Value),
|
||||
new SqlParameter("@zipentrega", operacion.Zipentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@monto", operacion.Monto ?? (object)DBNull.Value),
|
||||
new SqlParameter("@tarjeta", operacion.Tarjeta ?? (object)DBNull.Value),
|
||||
new SqlParameter("@fechaentrega", operacion.Fechaentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@emailcomprador", operacion.Emailcomprador ?? (object)DBNull.Value),
|
||||
new SqlParameter("@validanropuerta", operacion.Validanropuerta ?? (object)DBNull.Value),
|
||||
new SqlParameter("@ciudadentrega", operacion.Ciudadentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@validatipodoc", operacion.Validatipodoc ?? (object)DBNull.Value),
|
||||
new SqlParameter("@noperacion", operacion.Noperacion ?? (object)DBNull.Value),
|
||||
new SqlParameter("@estadoentrega", operacion.Estadoentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@resultado", operacion.Resultado ?? (object)DBNull.Value),
|
||||
new SqlParameter("@mensajeentrega", operacion.Mensajeentrega ?? (object)DBNull.Value),
|
||||
new SqlParameter("@precio", operacion.Precioneto ?? 0) // El SP espera int
|
||||
};
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(
|
||||
"EXEC dbo.sp_inserta_operaciones @fecha, @Motivo, @Moneda, @Direccionentrega, " +
|
||||
"@Validaciondomicilio, @codigopedido, @nombreentrega, @fechahora, @telefonocomprador, " +
|
||||
"@barrioentrega, @codautorizacion, @paisentrega, @cuotas, @validafechanac, @validanrodoc, " +
|
||||
"@titular, @pedido, @zipentrega, @monto, @tarjeta, @fechaentrega, @emailcomprador, " +
|
||||
"@validanropuerta, @ciudadentrega, @validatipodoc, @noperacion, @estadoentrega, " +
|
||||
"@resultado, @mensajeentrega, @precio",
|
||||
parameters);
|
||||
|
||||
_logger.LogInformation("Operación registrada correctamente: {Noperacion}", operacion.Noperacion);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al insertar operación: {Noperacion}", operacion.Noperacion);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Operacion>> ObtenerOperacionesPorNumeroAsync(string noperacion)
|
||||
{
|
||||
return await _context.Operaciones
|
||||
.AsNoTracking()
|
||||
.Where(o => o.Noperacion == noperacion)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<Operacion>> ObtenerOperacionesPorFechasAsync(DateTime fechaInicio, DateTime fechaFin)
|
||||
{
|
||||
return await _context.Operaciones
|
||||
.AsNoTracking()
|
||||
.Where(o => o.Fecha >= fechaInicio && o.Fecha <= fechaFin)
|
||||
.OrderByDescending(o => o.Fecha)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<MedioDePago>> ObtenerMediosDePagoAsync()
|
||||
{
|
||||
return await _context.MediosDePago
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,42 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom'; // Importar useNavigate
|
||||
import { AvisosService } from '../services/avisos.service';
|
||||
import { AdsV2Service } from '../services/ads.v2.service';
|
||||
import { AuthService, type UserSession } from '../services/auth.service';
|
||||
import type { DatosAvisoDto } from '../types/aviso.types';
|
||||
import FormularioAviso from '../components/FormularioAviso';
|
||||
import LoginModal from '../components/LoginModal';
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import { AvisosService } from "../services/avisos.service";
|
||||
import { AdsV2Service } from "../services/ads.v2.service";
|
||||
import { AuthService, type UserSession } from "../services/auth.service";
|
||||
import type { DatosAvisoDto } from "../types/aviso.types";
|
||||
import FormularioAviso from "../components/FormularioAviso";
|
||||
import LoginModal from "../components/LoginModal";
|
||||
|
||||
const TAREAS_DISPONIBLES = [
|
||||
{ id: 'EAUTOS', label: 'Automóviles', icon: '🚗', description: 'Venta de Autos, Camionetas y Utilitarios' },
|
||||
{ id: 'EMOTOS', label: 'Motos', icon: '🏍️', description: 'Venta de Motos, Cuatriciclos y Náutica' },
|
||||
{
|
||||
id: "EAUTOS",
|
||||
label: "Automóviles",
|
||||
icon: "🚗",
|
||||
description: "Venta de Autos, Camionetas y Utilitarios",
|
||||
},
|
||||
{
|
||||
id: "EMOTOS",
|
||||
label: "Motos",
|
||||
icon: "🏍️",
|
||||
description: "Venta de Motos, Cuatriciclos y Náutica",
|
||||
},
|
||||
];
|
||||
|
||||
export default function PublicarAvisoPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate(); // Hook de navegación
|
||||
const editId = searchParams.get('edit');
|
||||
const editId = searchParams.get("edit");
|
||||
|
||||
const [categorySelection, setCategorySelection] = useState<string>('');
|
||||
const [categorySelection, setCategorySelection] = useState<string>("");
|
||||
const [tarifas, setTarifas] = useState<DatosAvisoDto[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [planSeleccionado, setPlanSeleccionado] = useState<DatosAvisoDto | null>(null);
|
||||
const [planSeleccionado, setPlanSeleccionado] =
|
||||
useState<DatosAvisoDto | null>(null);
|
||||
const [fixedCategory, setFixedCategory] = useState<string | null>(null);
|
||||
const [user, setUser] = useState<UserSession | null>(AuthService.getCurrentUser());
|
||||
const [user, setUser] = useState<UserSession | null>(
|
||||
AuthService.getCurrentUser(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (editId) {
|
||||
@@ -31,30 +44,24 @@ export default function PublicarAvisoPage() {
|
||||
}
|
||||
}, [editId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (planSeleccionado) {
|
||||
window.scrollTo({ top: 0, behavior: "instant" });
|
||||
}
|
||||
}, [planSeleccionado]);
|
||||
|
||||
const cargarAvisoParaEdicion = async (id: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const ad = await AdsV2Service.getById(id);
|
||||
|
||||
// Determinamos la categoría para cargar las tarifas correspondientes
|
||||
const categoryCode = ad.vehicleTypeID === 1 ? 'EAUTOS' : 'EMOTOS';
|
||||
const categoryCode = ad.vehicleTypeID === 1 ? "EAUTOS" : "EMOTOS";
|
||||
|
||||
setCategorySelection(categoryCode);
|
||||
|
||||
// 🟢 FIX: Bloquear el cambio de categoría
|
||||
// Bloquear el cambio de categoría
|
||||
setFixedCategory(categoryCode);
|
||||
|
||||
// 🟢 FIX: NO seleccionamos plan automáticamente.
|
||||
// Dejamos que el usuario elija el plan en las tarjetas.
|
||||
// (Eliminamos todo el bloque de setPlanSeleccionado)
|
||||
|
||||
/* BLOQUE ELIMINADO:
|
||||
const tarifasData = await AvisosService.obtenerConfiguracion('EMOTORES', ad.isFeatured ? 1 : 0);
|
||||
const tarifaReal = tarifasData[0];
|
||||
if (!tarifaReal) throw new Error("Tarifa no encontrada");
|
||||
setPlanSeleccionado({ ... });
|
||||
*/
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Error al cargar el aviso.");
|
||||
@@ -71,8 +78,8 @@ export default function PublicarAvisoPage() {
|
||||
setError(null);
|
||||
try {
|
||||
const [simple, destacado] = await Promise.all([
|
||||
AvisosService.obtenerConfiguracion('EMOTORES', 0),
|
||||
AvisosService.obtenerConfiguracion('EMOTORES', 1)
|
||||
AvisosService.obtenerConfiguracion("EMOTORES", 0),
|
||||
AvisosService.obtenerConfiguracion("EMOTORES", 1),
|
||||
]);
|
||||
|
||||
const planes = [...simple, ...destacado];
|
||||
@@ -90,26 +97,30 @@ export default function PublicarAvisoPage() {
|
||||
}, [categorySelection]);
|
||||
|
||||
const handleSelectPlan = (plan: DatosAvisoDto) => {
|
||||
const vehicleTypeId = categorySelection === 'EAUTOS' ? 1 : 2;
|
||||
const nombrePlanAmigable = plan.paquete === 1 ? 'PLAN DESTACADO' : 'PLAN ESTÁNDAR';
|
||||
const vehicleTypeId = categorySelection === "EAUTOS" ? 1 : 2;
|
||||
const nombrePlanAmigable =
|
||||
plan.paquete === 1 ? "PLAN DESTACADO" : "PLAN ESTÁNDAR";
|
||||
|
||||
setPlanSeleccionado({
|
||||
...plan,
|
||||
idRubro: vehicleTypeId,
|
||||
nomavi: nombrePlanAmigable
|
||||
nomavi: nombrePlanAmigable,
|
||||
});
|
||||
};
|
||||
|
||||
// Manejador centralizado de éxito
|
||||
const handleSuccess = (adId: number, isAdminAction: boolean = false) => {
|
||||
const status = isAdminAction ? 'admin_created' : 'approved';
|
||||
const status = isAdminAction ? "admin_created" : "approved";
|
||||
navigate(`/pago-confirmado?status=${status}&adId=${adId}`);
|
||||
};
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-20 min-h-[60vh]">
|
||||
<LoginModal onSuccess={(u) => setUser(u)} onClose={() => navigate('/')} />
|
||||
<LoginModal
|
||||
onSuccess={(u) => setUser(u)}
|
||||
onClose={() => navigate("/")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -120,11 +131,15 @@ export default function PublicarAvisoPage() {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto py-8 px-6">
|
||||
<header className="flex justify-between items-center mb-10">
|
||||
<button onClick={() => setPlanSeleccionado(null)} className="text-gray-500 hover:text-white uppercase text-[10px] font-black tracking-widest flex items-center gap-2 transition-colors">
|
||||
<button
|
||||
onClick={() => setPlanSeleccionado(null)}
|
||||
className="text-gray-500 hover:text-white uppercase text-[10px] font-black tracking-widest flex items-center gap-2 transition-colors"
|
||||
>
|
||||
← Volver a Planes
|
||||
</button>
|
||||
<div className="glass px-4 py-2 rounded-xl text-xs border border-white/5">
|
||||
Publicando como: <span className="text-blue-400 font-bold">{user.username}</span>
|
||||
Publicando como:{" "}
|
||||
<span className="text-blue-400 font-bold">{user.username}</span>
|
||||
</div>
|
||||
</header>
|
||||
<FormularioAviso
|
||||
@@ -140,13 +155,17 @@ export default function PublicarAvisoPage() {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 md:px-6 py-8 md:py-12">
|
||||
<header className="mb-8 md:mb-16 text-center md:text-left">
|
||||
<h2 className="text-3xl md:text-6xl font-black tracking-tighter uppercase mb-2">Comienza a <span className="text-gradient">Vender</span></h2>
|
||||
<p className="text-gray-500 text-sm md:text-lg italic">Selecciona una categoría para ver los planes de publicación.</p>
|
||||
<h2 className="text-3xl md:text-6xl font-black tracking-tighter uppercase mb-2">
|
||||
Comienza a <span className="text-gradient">Vender</span>
|
||||
</h2>
|
||||
<p className="text-gray-500 text-sm md:text-lg italic">
|
||||
Selecciona una categoría para ver los planes de publicación.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* SECCIÓN DE CATEGORÍA */}
|
||||
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 mb-10 md:mb-20 text-white">
|
||||
{TAREAS_DISPONIBLES.map(t => {
|
||||
{TAREAS_DISPONIBLES.map((t) => {
|
||||
// Lógica de bloqueo
|
||||
const isDisabled = fixedCategory && fixedCategory !== t.id;
|
||||
|
||||
@@ -157,16 +176,24 @@ export default function PublicarAvisoPage() {
|
||||
disabled={!!isDisabled} // Deshabilitar botón
|
||||
className={`
|
||||
glass-card p-6 md:p-10 rounded-[2rem] md:rounded-[2.5rem] flex items-center justify-between group transition-all text-left
|
||||
${categorySelection === t.id ? 'border-blue-500 scale-[1.02] shadow-2xl shadow-blue-600/10 bg-white/5' : 'hover:bg-white/5'}
|
||||
${isDisabled ? 'opacity-30 cursor-not-allowed grayscale' : 'cursor-pointer'}
|
||||
${categorySelection === t.id ? "border-blue-500 scale-[1.02] shadow-2xl shadow-blue-600/10 bg-white/5" : "hover:bg-white/5"}
|
||||
${isDisabled ? "opacity-30 cursor-not-allowed grayscale" : "cursor-pointer"}
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
<span className="text-[10px] md:text-xs font-black uppercase tracking-widest text-blue-400 mb-1 md:mb-2 block">Categoría</span>
|
||||
<h3 className="text-2xl md:text-4xl font-bold mb-1 md:mb-2 uppercase tracking-tight">{t.label}</h3>
|
||||
<p className="text-gray-500 font-light text-xs md:text-sm">{t.description}</p>
|
||||
<span className="text-[10px] md:text-xs font-black uppercase tracking-widest text-blue-400 mb-1 md:mb-2 block">
|
||||
Categoría
|
||||
</span>
|
||||
<h3 className="text-2xl md:text-4xl font-bold mb-1 md:mb-2 uppercase tracking-tight">
|
||||
{t.label}
|
||||
</h3>
|
||||
<p className="text-gray-500 font-light text-xs md:text-sm">
|
||||
{t.description}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-4xl md:text-6xl group-hover:scale-110 transition-transform duration-300">{t.icon}</span>
|
||||
<span className="text-4xl md:text-6xl group-hover:scale-110 transition-transform duration-300">
|
||||
{t.icon}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -177,27 +204,38 @@ export default function PublicarAvisoPage() {
|
||||
<section className="animate-fade-in-up">
|
||||
<div className="flex justify-between items-end mb-10 border-b border-white/5 pb-4">
|
||||
<div>
|
||||
<h3 className="text-3xl font-black uppercase tracking-tighter">Planes <span className="text-blue-400">Disponibles</span></h3>
|
||||
<p className="text-gray-500 text-xs mt-1 uppercase tracking-widest">Para {categorySelection === 'EAUTOS' ? 'Automóviles' : 'Motos'}</p>
|
||||
<h3 className="text-3xl font-black uppercase tracking-tighter">
|
||||
Planes <span className="text-blue-400">Disponibles</span>
|
||||
</h3>
|
||||
<p className="text-gray-500 text-xs mt-1 uppercase tracking-widest">
|
||||
Para {categorySelection === "EAUTOS" ? "Automóviles" : "Motos"}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-gray-600 text-[10px] font-bold uppercase tracking-widest">Precios finales (IVA Incluido)</span>
|
||||
<span className="text-gray-600 text-[10px] font-bold uppercase tracking-widest">
|
||||
Precios finales (IVA Incluido)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/10 text-red-400 p-6 rounded-[2rem] border border-red-500/20 mb-10 text-center">
|
||||
<p className="font-bold uppercase tracking-widest text-xs mb-2">Error de Conexión</p>
|
||||
<p className="font-bold uppercase tracking-widest text-xs mb-2">
|
||||
Error de Conexión
|
||||
</p>
|
||||
<p className="italic text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center p-20"><div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div></div>
|
||||
<div className="flex justify-center p-20">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
{tarifas.map((tarifa, idx) => {
|
||||
const precioRaw = tarifa.importeTotsiniva > 0
|
||||
? tarifa.importeTotsiniva * 1.105
|
||||
: tarifa.importeSiniva * 1.105;
|
||||
const precioRaw =
|
||||
tarifa.importeTotsiniva > 0
|
||||
? tarifa.importeTotsiniva * 1.105
|
||||
: tarifa.importeSiniva * 1.105;
|
||||
const precioFinal = Math.round(precioRaw);
|
||||
const esDestacado = tarifa.paquete === 1;
|
||||
const tituloPlan = esDestacado ? "DESTACADO" : "ESTÁNDAR";
|
||||
@@ -206,14 +244,20 @@ export default function PublicarAvisoPage() {
|
||||
: "Presencia esencial. Tu aviso aparecerá en el listado general de búsqueda.";
|
||||
|
||||
return (
|
||||
<div key={idx} onClick={() => handleSelectPlan(tarifa)}
|
||||
className={`glass-card p-8 rounded-[2.5rem] flex flex-col group cursor-pointer relative overflow-hidden transition-all hover:-translate-y-2 hover:shadow-2xl ${esDestacado ? 'border-blue-500/30 hover:border-blue-500 hover:shadow-blue-900/20' : 'hover:border-white/30'}`}>
|
||||
|
||||
<div className={`absolute top-0 right-0 text-white text-[9px] font-black px-6 py-2 rounded-bl-3xl uppercase tracking-widest shadow-lg ${esDestacado ? 'bg-gradient-to-bl from-blue-600 to-cyan-500 animate-glow' : 'bg-white/10 text-gray-400'}`}>
|
||||
{esDestacado ? 'RECOMENDADO' : 'BÁSICO'}
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => handleSelectPlan(tarifa)}
|
||||
className={`glass-card p-8 rounded-[2.5rem] flex flex-col group cursor-pointer relative overflow-hidden transition-all hover:-translate-y-2 hover:shadow-2xl ${esDestacado ? "border-blue-500/30 hover:border-blue-500 hover:shadow-blue-900/20" : "hover:border-white/30"}`}
|
||||
>
|
||||
<div
|
||||
className={`absolute top-0 right-0 text-white text-[9px] font-black px-6 py-2 rounded-bl-3xl uppercase tracking-widest shadow-lg ${esDestacado ? "bg-gradient-to-bl from-blue-600 to-cyan-500 animate-glow" : "bg-white/10 text-gray-400"}`}
|
||||
>
|
||||
{esDestacado ? "RECOMENDADO" : "BÁSICO"}
|
||||
</div>
|
||||
|
||||
<h4 className={`text-3xl font-black uppercase tracking-tighter mb-4 mt-4 ${esDestacado ? 'text-blue-400' : 'text-white'}`}>
|
||||
<h4
|
||||
className={`text-3xl font-black uppercase tracking-tighter mb-4 mt-4 ${esDestacado ? "text-blue-400" : "text-white"}`}
|
||||
>
|
||||
{tituloPlan}
|
||||
</h4>
|
||||
|
||||
@@ -224,33 +268,61 @@ export default function PublicarAvisoPage() {
|
||||
|
||||
<ul className="space-y-3">
|
||||
<li className="flex justify-between text-xs text-gray-300 items-center">
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">Plataforma</span>
|
||||
<span className="font-bold bg-white/5 px-2 py-1 rounded text-[10px]">SOLO WEB</span>
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">
|
||||
Plataforma
|
||||
</span>
|
||||
<span className="font-bold bg-white/5 px-2 py-1 rounded text-[10px]">
|
||||
SOLO WEB
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex justify-between text-xs text-gray-300 items-center">
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">Duración</span>
|
||||
<span className="font-bold">{tarifa.cantidadDias} Días</span>
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">
|
||||
Duración
|
||||
</span>
|
||||
<span className="font-bold">
|
||||
{tarifa.cantidadDias} Días
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex justify-between text-xs text-gray-300 items-center">
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">Fotos</span>
|
||||
<span className="font-bold text-green-400">Hasta 5</span>
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">
|
||||
Fotos
|
||||
</span>
|
||||
<span className="font-bold text-green-400">
|
||||
Hasta 5
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex justify-between text-xs text-gray-300 items-center">
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">Visibilidad</span>
|
||||
<span className={`font-bold ${esDestacado ? 'text-blue-400' : 'text-gray-300'}`}>{esDestacado ? 'ALTA ⭐' : 'NORMAL'}</span>
|
||||
<span className="text-gray-500 uppercase tracking-wider font-bold text-[10px]">
|
||||
Visibilidad
|
||||
</span>
|
||||
<span
|
||||
className={`font-bold ${esDestacado ? "text-blue-400" : "text-gray-300"}`}
|
||||
>
|
||||
{esDestacado ? "ALTA ⭐" : "NORMAL"}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto pt-6 border-t border-white/5">
|
||||
<span className="text-gray-500 text-[10px] font-black uppercase tracking-widest block mb-1">Precio Final</span>
|
||||
<span className="text-gray-500 text-[10px] font-black uppercase tracking-widest block mb-1">
|
||||
Precio Final
|
||||
</span>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-4xl font-black tracking-tighter text-white">
|
||||
${precioFinal.toLocaleString('es-AR', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
|
||||
$
|
||||
{precioFinal.toLocaleString("es-AR", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
</span>
|
||||
<span className="text-xs font-bold text-gray-500">
|
||||
ARS
|
||||
</span>
|
||||
<span className="text-xs font-bold text-gray-500">ARS</span>
|
||||
</div>
|
||||
<button className={`w-full mt-6 text-white py-4 rounded-xl font-bold uppercase text-xs tracking-widest transition-all shadow-lg ${esDestacado ? 'bg-blue-600 hover:bg-blue-500 shadow-blue-600/20' : 'bg-white/5 hover:bg-white/10'}`}>
|
||||
<button
|
||||
className={`w-full mt-6 text-white py-4 rounded-xl font-bold uppercase text-xs tracking-widest transition-all shadow-lg ${esDestacado ? "bg-blue-600 hover:bg-blue-500 shadow-blue-600/20" : "bg-white/5 hover:bg-white/10"}`}
|
||||
>
|
||||
Seleccionar
|
||||
</button>
|
||||
</div>
|
||||
@@ -263,4 +335,4 @@ export default function PublicarAvisoPage() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user