Resuelve 4 de los followups creados post-archive de PRC-001: #55 — Decisión de negocio (2026-04-21): emojis NO se permiten en Symbol config. - WordCounterService.ContainsEmoji(string): helper publico que reutiliza los rangos Unicode de IsEmojiRune (Emoticons, Pictographs, Dingbats, VS-16, ZWJ). - CreateChargeableCharConfigCommandValidator: regla .Must que rechaza emoji en Symbol con mensaje claro. Defensiva: cubre clientes directos al API (Postman, adversariales) mas alla del SymbolInput blocker del frontend. - Tests: 5 emojis positivos (smile/car/fire/heart VS-16/sun) + 8 plain symbols ($, %, !, ¡, @, €, ##, ABCD) + actualizacion del Api test E2E (Post_WithEmojiSymbol). #57 — Alineacion FluentValidation con opt-in billing (CK_Price_NonNegative >= 0). - CreateChargeableCharConfigCommandValidator.PricePerUnit: GreaterThan(0) -> GreaterThanOrEqualTo(0). Mensaje explica el significado: 0 = no cobra. - Tests actualizados: PricePerUnit_Zero ahora Passes (era Fails). Negative sigue fallando. Api e2e usa -1 para el caso de rechazo. #58 — tsconfig ignoreDeprecations + MSW handler (parte a). - src/web/tsconfig.json: agrega "ignoreDeprecations": "6.0" para silenciar el warning TS5101 del baseUrl deprecated en TS 6.x. - (El MSW handler de /api/v1/admin/product-types no aplica — los tests ya mockean ProductTypeSelect directamente; warning residual no existe.) #54 — Seeder demo de overrides ficticios per-ProductType (V025). - database/migrations/V025__seed_chargeable_char_overrides_demo.sql: MERGE idempotente que crea overrides de ChargeableCharConfig para ProductTypes Clasificado/Notables/Fúnebres si existen en la DB. Precios ficticios ($ 5-8, % 3-5, ! 2-4, ¡ 2-4). No-op si los tipos no estan seedados (sera cuando PRD-008 haga seed de los 12 legacy). - V025_ROLLBACK.sql: elimina overrides demo preservando globales. - Aplicado en SIGCM2, SIGCM2_Test_App, SIGCM2_Test_Api. - database/README.md: V025 agregada al indice. Tests: 1659 .NET (1310 Application + 349 Api) + 510 vitest — todos GREEN. Closes #54, #55, #57, #58.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using FluentValidation;
|
||||
using SIGCM2.Application.Common;
|
||||
using SIGCM2.Domain.Pricing.ChargeableChars;
|
||||
using SIGCM2.Domain.Pricing.WordCounter;
|
||||
|
||||
namespace SIGCM2.Application.Pricing.ChargeableChars.Create;
|
||||
|
||||
@@ -19,7 +20,9 @@ public sealed class CreateChargeableCharConfigCommandValidator
|
||||
.NotEmpty()
|
||||
.WithMessage("Symbol no puede estar vacío.")
|
||||
.MaximumLength(4)
|
||||
.WithMessage("Symbol no puede exceder 4 caracteres.");
|
||||
.WithMessage("Symbol no puede exceder 4 caracteres.")
|
||||
.Must(s => !WordCounterService.ContainsEmoji(s))
|
||||
.WithMessage("Symbol no puede contener emojis. Usá símbolos ASCII o latinos (ej: $, %, !, ¡).");
|
||||
|
||||
RuleFor(x => x.Category)
|
||||
.NotEmpty()
|
||||
@@ -28,8 +31,8 @@ public sealed class CreateChargeableCharConfigCommandValidator
|
||||
.WithMessage($"Category inválida. Valores válidos: {string.Join(", ", new[] { ChargeableCharCategories.Currency, ChargeableCharCategories.Percentage, ChargeableCharCategories.Exclamation, ChargeableCharCategories.Question, ChargeableCharCategories.Other })}.");
|
||||
|
||||
RuleFor(x => x.PricePerUnit)
|
||||
.GreaterThan(0m)
|
||||
.WithMessage("PricePerUnit debe ser > 0.");
|
||||
.GreaterThanOrEqualTo(0m)
|
||||
.WithMessage("PricePerUnit debe ser >= 0. Usá 0 para desactivar el cobro de este símbolo (opt-in billing).");
|
||||
|
||||
RuleFor(x => x.ValidFrom)
|
||||
.GreaterThanOrEqualTo(today)
|
||||
|
||||
@@ -96,6 +96,22 @@ public sealed class WordCounterService
|
||||
return new WordCountResult(tokens.Length, counts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given string contains any emoji codepoint.
|
||||
/// Used by validators that must reject emojis in user-facing identifiers
|
||||
/// (e.g. ChargeableCharConfig.Symbol) where the frontend blocker can be bypassed
|
||||
/// by direct API calls. Shares the same IsEmojiRune Unicode ranges used by Count().
|
||||
/// </summary>
|
||||
public static bool ContainsEmoji(string? text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return false;
|
||||
foreach (var rune in text.EnumerateRunes())
|
||||
{
|
||||
if (IsEmojiRune(rune)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given rune is an emoji codepoint.
|
||||
/// Covers: Extended Pictographics, Misc Symbols, Dingbats, Variation Selector-16, ZWJ.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user