@ -4,10 +4,9 @@
< a -col :xs ="22" :sm ="20" :md ="20" :lg ="16" :xl ="14" >
< div class = "auth-shell" >
< a -row : gutter = "[0, 0]" class = "auth-layout" >
<!-- FORM -- >
< a -col :xs ="24" :md ="12" class = "auth-pane auth-pane-form" >
< div class = "pane-inner" >
< div class = "brand" >
< div class = "brand-mark" >
< img
@ -51,7 +50,15 @@
>
<!-- DNI -- >
< a -form -item v-if ="isRegister" label="DNI" name="dni" >
< a -input v -model :value ="formState.dni" size = "large" placeholder = "Ingrese su DNI" >
< a -input
v - model : value = "formState.dni"
size = "large"
placeholder = "Ingrese su DNI"
inputmode = "numeric"
: maxlength = "8"
autocomplete = "off"
@ input = "onDniInput"
>
< template # prefix > < IdcardOutlined / > < / template >
< / a - i n p u t >
< / a - f o r m - i t e m >
@ -61,30 +68,45 @@
v - model : value = "formState.name"
size = "large"
placeholder = "Ingrese su nombre completo"
autocomplete = "name"
>
< template # prefix > < UserOutlined / > < / template >
< / a - i n p u t >
< / a - f o r m - i t e m >
< a -form -item label = "Correo electrónico" name = "email" >
< a -input v -model :value ="formState.email" size = "large" placeholder = "correo@ejemplo.com" >
< a -input
v - model : value = "formState.email"
size = "large"
placeholder = "correo@ejemplo.com"
autocomplete = "username"
>
< template # prefix > < MailOutlined / > < / template >
< / a - i n p u t >
< / a - f o r m - i t e m >
<!-- PASSWORD -- >
< a -form -item label = "Contraseña" name = "password" >
< a -input -password
v - model : value = "formState.password"
size = "large"
placeholder = "Ingrese su contraseña"
: autocomplete = "isRegister ? 'new-password' : 'current-password'"
>
< template # prefix > < LockOutlined / > < / template >
< / a - i n p u t - p a s s w o r d >
<!-- Solo en registro : requisitos ( sin títulos / alertas / tips ) -- >
< ul v-if ="isRegister" class="pwd-req" >
< li :style ="reqStyle(hasMinLength)" > Mínimo 8 caracteres < / li >
< li :style ="reqStyle(hasUpper)" > Al menos 1 mayúscula ( A - Z ) < / li >
< li :style ="reqStyle(hasLower)" > Al menos 1 minúscula ( a - z ) < / li >
< li :style ="reqStyle(hasNumber)" > Al menos 1 número ( 0 - 9 ) < / li >
< li :style ="reqStyle(hasSpecial)" > Al menos 1 símbolo ( @ $ ! % * ? & ) < / li >
< / ul >
< / a - f o r m - i t e m >
<!-- CONFIRM PASSWORD -- >
< a -form -item
v - if = "isRegister"
label = "Confirmar contraseña"
@ -94,26 +116,17 @@
v - model : value = "formState.password_confirmation"
size = "large"
placeholder = "Repita su contraseña"
autocomplete = "new-password"
>
< template # prefix > < LockOutlined / > < / template >
< / a - i n p u t - p a s s w o r d >
< / a - f o r m - i t e m >
< a -row
v - if = "!isRegister"
justify = "space-between"
align = "middle"
class = "form-row"
>
<!-- ROW ( LOGIN ) -- >
< a -row v-if ="!isRegister" justify="space-between" align="middle" class="form-row" >
< a -checkbox v -model :checked ="rememberMe" > Recordarme < / a - c h e c k b o x >
< a -button
type = "link"
size = "small"
class = "link-muted"
@ click = "handleForgotPassword"
>
< a -button type = "link" size = "small" class = "link-muted" @click ="handleForgotPassword" >
¿ Olvidó su contraseña ?
< / a - b u t t o n >
< / a - r o w >
@ -137,68 +150,57 @@
< / div >
< / a - c o l >
< a -col :xs ="24" :md ="12" class = "auth-pane auth-pane-info" >
< div class = "pane-inner pane-inner-info" >
< div class = "info-top" >
< a -tag class = "info-tag" > Universidad Nacional del Altiplano – Puno < / a - t a g >
< a -typography -title :level ="3" style = "margin: 8px 0 0" >
{ { isRegister ? "Registro de Postulante" : "Portal del Postulante" } }
< / a - t y p o g r a p h y - t i t l e >
< a -typography -text type = "secondary" >
{ {
isRegister
? "Crea tu cuenta para participar en el proceso de admisión y acceder a todos los servicios del portal."
: "Ingresa al portal para gestionar tu inscripción, revisar procesos disponibles y rendir un test de referencia."
} }
< / a - t y p o g r a p h y - t e x t >
< / div >
<!-- INFO -- >
< a -col :xs ="24" :md ="12" class = "auth-pane auth-pane-info" >
< div class = "pane-inner pane-inner-info" >
< div class = "info-top" >
< a -tag class = "info-tag" > Universidad Nacional del Altiplano – Puno < / a - t a g >
< div class = "info-section" >
< div class = "info-section-title" >
{ { isRegister ? "Al registrarte podrás" : "Al ingresar podrás" } }
< / div >
< div class = "info-list" >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Rendir un test de referencia < / b > . < / span >
< / div >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Ver procesos disponibles < / b > según tu modalidad . < / span >
< / div >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Consultar tu estado < / b > de inscripción y seguimiento del proceso . < / span >
< / div >
<!-- < div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Ver tu resultado detallado < / b > por cursos . < / span >
< / div > -- >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Revisar comunicados oficiales < / b > del proceso de admisión . < / span >
< / div >
< / div >
< / div >
< a -typography -title :level ="3" style = "margin: 8px 0 0" >
{ { isRegister ? "Registro de Postulante" : "Portal del Postulante" } }
< / a - t y p o g r a p h y - t i t l e >
< div class = "info-foot" >
< a -typography -text type = "secondary" >
Plataforma oficial de admisión • Soporte en horario institucional
< / a - t y p o g r a p h y - t e x t >
< / div >
< a -typography -text type = "secondary" >
{ {
isRegister
? "Crea tu cuenta para participar en el proceso de admisión y acceder a los servicios del portal."
: "Ingresa al portal para ver tu estado de inscripción, revisar procesos disponibles y rendir un test de referencia."
} }
< / a - t y p o g r a p h y - t e x t >
< / div >
< div class = "info-section" >
< div class = "info-section-title" >
{ { isRegister ? "Al registrarte podrás" : "Al ingresar podrás" } }
< / div >
< / a - c o l >
< div class = "info-list" >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Rendir un test de referencia < / b > . < / span >
< / div >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Ver procesos disponibles < / b > . < / span >
< / div >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Consultar tu estado < / b > de inscripción y seguimiento del proceso . < / span >
< / div >
< div class = "info-item" >
< span class = "info-bullet" > < / span >
< span > < b > Revisar comunicados oficiales < / b > del proceso de admisión . < / span >
< / div >
< / div >
< / div >
< div class = "info-foot" >
< a -typography -text type = "secondary" >
Plataforma oficial de admisión • Soporte en horario institucional
< / a - t y p o g r a p h y - t e x t >
< / div >
< / div >
< / a - c o l >
< / a - r o w >
< / div >
< / a - c o l >
@ -221,7 +223,6 @@ const isRegister = ref(false);
const rememberMe = ref ( false ) ;
const loading = ref ( false ) ;
const logoSrc = "/logotiny.png" ;
const logoError = ref ( false ) ;
@ -243,6 +244,17 @@ watch(isRegister, () => {
formRef . value ? . clearValidate ( ) ;
} ) ;
const onDniInput = ( ) => {
formState . dni = ( formState . dni || "" ) . replace ( /\D/g , "" ) . slice ( 0 , 8 ) ;
} ;
/ / R e q u i s i t o s v i s u a l e s ( m i s m o s d e l b a c k e n d )
const hasMinLength = computed ( ( ) => ( formState . password || "" ) . length >= 8 ) ;
const hasUpper = computed ( ( ) => / [ A - Z ] / . test ( formState . password || "" ) ) ;
const hasLower = computed ( ( ) => / [ a - z ] / . test ( formState . password || "" ) ) ;
const hasNumber = computed ( ( ) => / \ d / . test ( formState . password || "" ) ) ;
const hasSpecial = computed ( ( ) => / [ @ $ ! % * ? & ] / . test ( formState . password || "" ) ) ;
const rules = computed ( ( ) => ( {
dni : [
{ required : isRegister . value , message : "Ingrese su DNI" , trigger : "blur" } ,
@ -258,20 +270,36 @@ const rules = computed(() => ({
] ,
password : [
{ required : true , message : "Ingrese su contraseña" , trigger : "blur" } ,
{ min : 6 , message : "La contraseña debe tener al menos 6 caracteres" , trigger : "blur" } ,
] ,
password _confirmation : [
{ required : isRegister . value , message : "Confirme su contraseña" , trigger : "blur" } ,
{
/ / S o l o e n r e g i s t r o v a l i d a m o s c o m p o s i c i ó n ; e n l o g i n n o m o l e s t a m o s .
validator : ( rule , value ) => {
if ( ! isRegister . value ) return Promise . resolve ( ) ;
if ( ! value ) return Promise . reject ( "Confirme su contraseña" ) ;
if ( value !== formState . password ) return Promise . reject ( "Las contraseñas no coinciden" ) ;
return Promise . resolve ( ) ;
const v = value || "" ;
const ok =
v . length >= 8 &&
/[A-Z]/ . test ( v ) &&
/[a-z]/ . test ( v ) &&
/\d/ . test ( v ) &&
/[@$!%*?&]/ . test ( v ) ;
return ok ? Promise . resolve ( ) : Promise . reject ( "Contraseña inválida" ) ;
} ,
trigger : "blur" ,
} ,
] ,
password _confirmation : [
{ required : isRegister . value , message : "Confirme su contraseña" , trigger : "blur" } ,
{
validator : ( rule , value ) => {
if ( ! isRegister . value ) return Promise . resolve ( ) ;
if ( ! value ) return Promise . resolve ( ) ; / / e l r e q u i r e d y a s e e n c a r g a
if ( value !== formState . password ) return Promise . reject ( "Las contraseñas no coinciden" ) ;
return Promise . resolve ( ) ;
} ,
trigger : "blur" ,
} ,
] ,
} ) ) ;
const showNotification = ( type , message , description = "" ) => {
@ -288,13 +316,18 @@ const handleForgotPassword = () => {
showNotification ( "info" , "Recuperación de contraseña" , "Por favor, contacte al administrador del sistema" ) ;
} ;
const reqStyle = ( ok ) => ( {
color : ok ? "#16a34a" : "#6b7280" ,
fontWeight : ok ? "700" : "500" ,
} ) ;
const handleSubmit = async ( ) => {
loading . value = true ;
try {
if ( isRegister . value ) {
const result = await authStore . register ( { ... formState } ) ;
if ( result . success ) {
showNotification ( "success" , "¡Registro exitoso!" , "Tu cuenta ha sido creada correctamente" );
showNotification ( "success" , "¡Registro exitoso!" );
toggleMode ( ) ;
} else {
showNotification ( "error" , "Error en registro" , result . error ) ;
@ -377,10 +410,6 @@ checkExistingAuth();
gap : 14 px ;
}
. pane - inner - info {
justify - content : space - between ;
}
/* Branding */
. brand {
display : flex ;
@ -426,10 +455,6 @@ checkExistingAuth();
font - size : 0.95 rem ;
}
. auth - header {
text - align : left ;
}
. auth - divider {
margin : 6 px 0 14 px ;
}
@ -474,29 +499,15 @@ checkExistingAuth();
font - weight : 800 ;
}
. info - top {
text - align : left ;
}
. info - tag {
border : 0 ;
background : color- mix ( in srgb , var ( -- ant - colorPrimary ) 14 % , transparent ) ;
background : rgba ( 22 , 119 , 255 , 0.12 ) ;
color : var ( -- ant - colorPrimary , # 1677 ff ) ;
font - weight : 800 ;
border - radius : 999 px ;
padding : 6 px 12 px ;
}
. info - section {
margin - top : 8 px ;
}
. info - section - title {
font - weight : 800 ;
color : var ( -- ant - colorTextHeading , # 111827 ) ;
margin : 6 px 0 10 px ;
}
. info - list {
margin - top : 10 px ;
display : grid ;
@ -525,6 +536,11 @@ checkExistingAuth();
border - top : 1 px solid var ( -- ant - colorBorderSecondary , rgba ( 0 , 0 , 0 , 0.06 ) ) ;
}
. pwd - req {
margin : 8 px 0 0 16 px ;
font - size : 12 px ;
}
@ media ( max - width : 768 px ) {
. auth - pane {
padding : 22 px ;
@ -537,10 +553,4 @@ checkExistingAuth();
min - height : auto ;
}
}
@ supports not ( color : color - mix ( in srgb , white 50 % , black ) ) {
. info - tag {
background : rgba ( 22 , 119 , 255 , 0.12 ) ;
}
}
< / style >